PHP Image Adaptation for Mobile

In my previous post I talked in general terms about using client side techniques for style adaptation and server side for content adaptation. On a simple site like this, the main type of content that needs to be adapted comprises the images.

What follows is a description of the rough and ready approach I use for adapting images on the server for the mobile or desktop context. It's still work in progress and when I've cleaned it up a bit I'll update this page but it seems to work OK and I'd like to 'show my working'!

PHP version 4.3 onwards includes the GD Extension by default. To check whether your PHP build includes it, load your phpinfo() page and look for something like this:

A table showing the status of various PHP features related to the GD graphics library

The GD section of the phpinfo() for this site

If you have this in your PHP installation (and you probably do), it's relatively easy to perform server side image adaptation.

The Basic Code

I began with an example from Programming PHP.

<?php
 $src = ImageCreateFromJPEG('foo.jpg');
 $width = ImageSx($src);
 $height = ImageSy($src);
 $x = $width/2; $y = $height/2;
 $dst = ImageCreateTrueColor($x,$y);
 ImageCopyResampled($dst,$src,0,0,0,0,$x,$y,$width,$height);
 header('Content-Type: image/png');
 ImagePNG($dst);
?>

Example 9-11. Resizing with ImageCopyResampled()

The first line takes the filename of the original image which can be either a JPG or PNG and creates a copy within PHP that can be manipulated. In this case, the file foo.jpg is assigned to the $src variable. Given that $src variable we can find the image's intrinsic dimensions. To rescale the image, we need to multiple those dimensions by some factor. Applying the same factor to both height and width will maintain the aspect ratio.

Once we know how big we want the output to be ($x and $y), we can create the new (resized) image which is what the next two lines do. As an aside, the ImageCopyResampled() function allows you to take a portion of the original image, i.e. crop it - that series of comma-separated 0s comprise coordinates - but doing that assumes you have detailed knowledge of each image and so is not likely to be useful in creating a generic function to reduce the size of whatever image you throw at it.

Finally the little routine here writes the content header and outputs the image itself.

Application

So much for the example. How can we use this in the real world? One thing it's important to emphasise is that mobile Web best practice is explicit on one point: the dimensions of images must be specified in the markup (i.e. img elements must include the width and height attributes) so any functions we write to handle image resizing must output markup as well as the image.

The reasons why this is important are that:

No ifs or buts: include dimensions in img elements.

Following this particular best practice leads me to implement server-side image adaptation as follows.

For mobile, I define 2 maximum image sizes:

These are defined in global variables called $maxWithFloatedImgMobile; $maxWithMainImgMobile; (and yes I know there's a typo there!).

If I were following mobile Web best practice to the letter, then I'd make the maximum width 120 pixels in both cases but, well I cheat a little and allow 'full screen images' to be 300 pixels wide. Allowing images to be as much as 300 pixels wide on mobile does mean that some older devices are likely to cause the user to need to scroll horizontally - but it's a small and decreasing number such devices.

So, I have a function called getImageDims() that takes two parameters: the filename of the image to be adapted (or not) and whether the image may be floated within the page. To these two parameters and the maximum width global variables we need to add just one more global variable which is $mobile_browser which is true if we're on a mobile device (see previous post for details of the function I use to detect mobile).

Taking that data we can now adapt the earlier example to create our function:

function getImageDims($file, $float) {
  global $mobile_browser;
  global $maxWithFloatedImgMobile;
  global $maxWithMainImgMobile;

  $suffix = substr($file, -3);
  $src; $x; $y;
  if ($suffix == 'png') {
    $src = ImageCreateFromPNG($file);
  } elseif ($suffix == 'jpg') {
    $src = ImageCreateFromJPEG($file);
  }
  $width = ImageSx($src);
  $height= ImageSy($src);
  if ((($mobile_browser > 0) && ($float)) && ($width > $maxWithFloatedImgMobile)) {
    $x = $maxWithFloatedImgMobile;
    $y = floor($height * ($x/$width));
  } elseif (($mobile_browser > 0) && ($width > $maxWithMainImgMobile)) {
    $x = $maxWithMainImgMobile;
    $y = floor($height * ($x/$width));
  } else {
    $x = $width;
    $y = $height;
  }
  return array('handle' => $src,
               'suffix'  => $suffix,
               'width'  => $width,
               'height' => $height,
               'x'      => $x,
               'y'      => $y);
}

This can handle either JPG or PNG. Since this only processes internal content I don't need to worry about trapping other image types or files with different suffixes. Notice that this function doesn't actually create the resized image, it just works out the dimensions that it will be (the values of $x and $y). Incidentally, I use the floor() function to return whole numbers - we don't want any fractional image dimensions!

To actually generate and print out the image, we need a standalone PHP file that can be the value of the src attribute of an img element. Mine looks like this:

<?php
include 'lib-pa.inc';

global $docRoot;

parse_str($_SERVER['QUERY_STRING']);

$_ = $docRoot . $src;
$hash = getImageDims($_, $float);

$dst = ImageCreateTrueColor($hash['x'], $hash['y']);
ImageCopyResampled($dst,$hash['handle'],0,0,0,0,$hash['x'],$hash['y'],$hash['width'],$hash['height']);
$mime = 'Content-Type: image/' . $hash['suffix'];
header($mime);
ImagePNG($dst);

// for debugging
//header('Content-type: text/plain');  
//  echo("We have an src of $_ and x and y are " . $hash['x'] .'and ' . $hash['y']);
?>

This file is called from normal img elements that look a bit like this example:

<img src="/inc/showImage.php?src=/diary/images/logo_isa.png&float=1&w=384&h=92" width="384" height="92" alt="ISA: Interoperability Solutions for European Public Administrations" />

Which adds in this image:

ISA: Interoperability Solutions for European Public Administrations

That img element is written by a third PHP function that I'll describe shortly but let's stick with the one we're focussed on now which is the entirety of the content of a file on this site called showImage.php.

This begins by including the library that contains the getImageDims() function and more. It gets its input from the query string which, in this case, sets parameters as follows:

src = /diary/images/logo_isa.png
float=1
w=384
h=92

I think you can see what's going on here: it says to the showImage.php file: take the image available at /diary/images/logo_isa.png and resize it if necessary. It is currently 384 x 92 pixels and is used in a floating context. Incidentally, I include the intrinsic dimensions in the query string so that when the image is cached, only the specific size image is cached, otherwise switching between mobile and desktop view would always show whichver version you got first and stored in the cache.

The global variable $docRoot tells PHP where to find files on my server. Append /diary/images/logo_isa.png to it and, with luck, the image will be found! That's what we pass to the getImageDims() function (from the library) along with the $float flag to work out what size the output image should be and tell us what type of image it is etc. This information is held in the $hash array (yes, I learned Perl first).

Now we can use ImageCopyResampled() to create the image and send it to the output in the way described in the original example from the PHP Programming book.

The Final Function

Remember that awkward fact of life that img elements really SHOULD set the dimensions of the image? To do that, you have to not only resize the image dynamically if the user is on a mobile device, you also have to write the img element itself dynamically, so there's one more function we need.

function writeImgElement($src, $alt, $float, $class) {
  global $docRoot;
  $_ =$docRoot . $src;
  $hash = getImageDims($_, $float);

  echo('<img src="/inc/showImage.php?src=' . $src . '&float=' . $float . '&w=' . $hash['x'] . '&h=' . $hash['y'] .'" width="' . $hash['x'] . '" height="' . $hash['y'] . '" alt="' . $alt . '"');
  if ($class) {
    echo(' class="' . $class . '"');
  }
  echo(' />');
}

This is called from within the source code of the Web page thus:

<?php writeImgElement('/diary/images/logo_isa.png', 'ISA: Interoperability Solutions for European Public Administrations', 1, ''); ?>

We pass the location of the original image relative to the document root (i.e. the way Web developers naturally refer to the location of images), plus the alt text, the float flag and any CSS class that might be relevant. It is necessary to call the getImageDims() function again to, well, get the image dimensions, so this may seem a little wasteful. The alternative is to store the info but for a site as simple as mine, that seems unnecessary. Given the output of the getImageDims() function and the information passed directly, writeImgElement() has all the details it needs to write the img element as we saw a little above:

<img src="/inc/showImage.php?src=/diary/images/logo_isa.png&amp;float=1&amp;w=384&amp;h=92" width="384" height="92" alt="ISA: Interoperability Solutions for European Public Administrations" />

Summary

The technique I've described here uses a total of three functions: one to write the HTML img element, one to actually resize and output the image and a helper function that provides information about the dimensions of the image to both of those. I am sure it can be done better - for a start I need to handle HTML5's figure and figcaption elements, but the basics are in place and it seems to work.

Comments welcome via e-mail which I will publish here

Tedd Sperling wrote:

Question: Why the required dimensions in the resultant image tag?

If your script delivers an image that is the size required for the device, then doesn't the device presentation-engine use the image "as-is" to present that image? Why the additional dimension instruction? It seems redundant.

For example, if I present an image (without dimensions ) to any Browser, it just shows the image "as-is". On the other hand, if I show the same image with dimensions, then the Browser is required to consider those dimensions before presentation -- doesn't that require more processing?

My position has always been to leave image tag absent of any dimensions and let the Browser present the image as it is -- is this wrong thinking? Please show me where I'm wrong.

On last thing -- I often dimension images within CSS using ems. I do this for accessibility where the user has vision problems and wants the page presented at a higher zoom. Using ems allows me to do that. Your article goes contrary to my practice. What's the problem with using image dimensions with css?

Many thanks for your consideration and answers.

Thanks for the question, Tedd.

My source for this is the relevant Mobile Web Best Practice (IMAGES_SPECIFY_SIZE] which says:

Images such as bitmaps have an intrinsic size. Telling the browser in advance what the size is avoids it having to re-flow the page when it receives it. Resizing images at the server reduces the amount of data transferred and the amount of processing the device has to carry out to scale the image.

Web pages are rendered in several steps so that the user sees 'something' as quickly as possible with things like images and styling added as they become available. You sometimes notice this on desktop/broadband but it can be even more noticeable on mobile/slow connections. To achieve this, my understanding is that browsers typically render the basic HTML first and at that point, unless you have specified width and height, it will leave a 'default' image space. That's 'Pass One'.

When the image arrives, its dimensions are accessed and the space left in the page for it adjusted accordingly which will very likely mean that the content has to be reflowed. That's Pass Two.

Then when the CSS is available it might change the image size again, float it or whatever and, well, that's pass three.

By providing image dimensions you avoid at least pass two which is relatively expensive in computational terms (and therefore heavy on the battery).

So no, it requires less processing if you include the image dimensions.

The advice is:

  1. specify image dimensions in the markup;
  2. make sure that they are the same as the intinsic dimensions (so no further processing is necessary by the browser).

If you want to include images of different sizes, which is definitely a good thing for the reasons you give, then the better way to do it is to link to different size images (this is something I really ought to do on my own site!)

HTH