Proportionally constrain image based on fixed width or height using PHP

Many colorful and empty frames stacked next to and behind each other.
Select-a-size frames for all your image needs.

The following post isn’t WordPress specific, however I’m using this in a WordPress environment, so you’ll find several references to it.

Array containing the file details

I have a custom meta box that allows a user to upload an image (or any file, really) to a post, but it doesn’t store the image as an attachment in the built-in media library. Instead, it stores the image in a predetermined location in the uploads folder and associates the image to the post using a post meta field.

The file can then be retrieved by supplying a post ID and field name to the WordPress get_post_meta() function. The post meta field contains a serialized array that looks something like this:

Array
(
    [post_id] => 84612
    [name] => test-post-1362694023.jpg
    [old_name] => Apple Glow.jpg
    [relative_path] => meta/post-thumbnail/2013/test-post-1362694023.jpg
    [created_date] => 2013-03-07 16:07:03
    [mime_type] => image/jpeg
    [file_size] => 14214
    [image_width] => 180
    [image_height] => 113
)

As a side note, the image is renamed during the upload based on the post title, if one exists. This was to ensure that all images uploaded via this meta box would be SEO friendly.

Once I have the array, I pass it to another function that will convert it to the desired output.

function convert_post_meta_file($file, $mode = 'auto', $args = array());

If you just pass it the image file, it automatically determines that an image tag would be best based on the mime type, however you can overwrite that to be any one of the other predefined modes. You can also pass in arguments to overwrite any defaults for that mode.

For example, let’s say your image is 180×113 pixels, but you wanted to force the height to be fixed to 60 pixels. You could pass that image attribute in via the third parameter of the function:

$args = array(
  'attributes' => array(
    'height' => 60
  )
);
$img = convert_post_meta_file($file, 'image', $args);

If you printed out the value of $img, it would look like this:

<img width="96" height="60" src="http://url.com/wp-content/uploads/meta/post-thumbnail/2013/test-post-1362694023.jpg" alt="Test Post" />

As you can see, the image was forced into the new dimensions. It’s important to note that it doesn’t actually create a new image of that size, but it simply constrains it. I use this for displaying images like this in different areas and context, but they are usually very similar to the original size.

Let’s look at how the convert function works.

Convert the file array to HTML

First we’re going to create a variable called $output to hold all of our output. Then we’ll do a quick check to make sure that there is a file and that it’s in the form of an array.

Check for a valid file

function convert_post_meta_file($file, $mode = 'auto', $args = array()) {
  $output = '';
  if(empty($file)) {
    return $output;
  }
  if(!is_array($file)) {
    return $output;
  }
}

Determine the file type

Next we’ll check whether the $mode variable is set to auto, which means that we need to check for what mode we need to display the file in based on the mime type.

function convert_post_meta_file($file, $mode = 'auto', $args = array()) {
  <!-- other stuff here -->
  if(isset($file['mime_type']) && $mode == 'auto') {
    $mode = convert_mime_type_to_group_name($file['mime_type']);
  }
}

At this point we have a mode that we can use. The convert_mime_type_to_group_name() function simply contains an array of mime types that are mapped to an appropriate mode that will be returned accordingly.

Determine how to present the file

Now that we have the mode, we can process the file with another, more specific function.

function convert_post_meta_file($file, $mode = 'auto', $args = array()) {
  <!-- other stuff here -->
  switch($mode) {

    case 'audio':
      $output = convert_post_meta_file_to_audio($file, $args);
      break;

    case 'image':
      $output = convert_post_meta_file_to_image($file, $args);
      break;

    case 'link':
      $output = convert_post_meta_file_to_link($file, $args);
      break;

    case 'url':
      $output = convert_post_meta_file_to_url($file);
      break;

    case 'video':
      $output = convert_post_meta_file_to_video($file, $args);
      break;

    default:
      $output = convert_post_meta_file_to_link($file, $args);
      break;

  }
  return $output;
}

Since we supplied the convert function with an image, it’s going to pass the file and its arguments to the convert_post_meta_file_to_image() function.

Convert the image array to HTML output

Once there, we’ll start with creating an array that’s going to hold all essential values that we’ll need for processing. If I know I need more than one or two variables and they’re all related, I prefer an array over creating many different variables.

function convert_post_meta_file_to_image($file, $args) {
  $image = array(
    'actual_width' => 0,
    'actual_height' => 0,
    'ratio' => 0,
    'target_width' => 0,
    'target_height' => 0
  );
}

Retrieve the image URL

Next I’m going convert the $file to a URL, as I’ll need that for the image tag later and to determine the image size if it wasn’t stored in the $file array for some reason.

function convert_post_meta_file_to_image($file, $args) {
  <!-- other stuff here -->
  $file_url = convert_post_meta_file_to_url($file);
}

Retrieve the actual image size

Let’s figure out what the actual image size is. We’ll check to see if either the width or height is missing in our array, and if so, use PHP’s getimagesize() function to obtain those values. Then we’ll store them in our temporary array as actual_width and actual_height.

function convert_post_meta_file_to_image($file, $args) {
  <!-- other stuff here -->
  if(!isset($file['image_width']) || !isset($file['image_height'])) {
    $image_size = getimagesize($file_url);
    if(isset($image_size[0])) {
      $image['actual_width'] = $image_size[0];
    }
    if(isset($image_size[1])) {
      $image['actual_height'] = $image_size[1];
    }
  } else {
    $image['actual_width'] = $file['image_width'];
    $image['actual_height'] = $file['image_height'];
  }
}

Calculate the image width to height ratio

Now we can calculate the ratio between the image width and height, so we can use this value to determine the new size.

function convert_post_meta_file_to_image($file, $args) {
  <!-- other stuff here -->
  $image['ratio'] = $image['actual_width'] / $image['actual_height'];
}

Retrieve the target image size

With the ratio stored, we can check to see if there is a width and/or height provided in the arguments. We’ll also use this opportunity to check and make sure that the new size isn’t greater than the size of our actual image.

function convert_post_meta_file_to_image($file, $args) {
  <!-- other stuff here -->
  if(isset($args['attributes']['width']) 
  && $args['attributes']['width'] < $image['actual_width']) {
    $image['target_width'] = $args['attributes']['width'];
  }
  if(isset($args['attributes']['height']) 
  && $args['attributes']['height'] < $image['actual_height']) {
    $image['target_height'] = $args['attributes']['height'];
  }
}

Calculate the new image size

Loaded with our actual and target image size, we can proceed with saving the image attributes we’re going to use to set the image size.

The different scenarios are as follows:

  1. If we have a target width and height, simply use what was provided, assuming that the person who provided them knows what they’re doing.
  2. If only a width was provided, we need to calculate the height using our ratio.
  3. If only a height was provided, we need to calculate the width using our ratio.
  4. If all else fails, simply display the image using the actual size.
function convert_post_meta_file_to_image($file, $args) {
  <!-- other stuff here -->
  if($image['target_width'] && $image['target_height']) {
    $args['attributes']['width'] = $image['target_width'];
    $args['attributes']['height'] = $image['target_height'];
  } else if($image['target_width']) {
    $args['attributes']['width'] = $image['target_width'];
    $args['attributes']['height'] = round($image['target_width'] / $image['ratio']);
  } else if($image['target_height']) {
    $args['attributes']['width'] = round($image['target_height'] * $image['ratio']);
    $args['attributes']['height'] = $image['target_height'];
  } else {
    $args['attributes']['width'] = $image['actual_width'];
    $args['attributes']['height'] = $image['actual_height'];
  }
}

Add the alt attribute to the image

I’m also going to set the alt attribute of the image using the post title of the page, provided no other alt was provided in $args.

function convert_post_meta_file_to_image($file, $args) {
  <!-- other stuff here -->
  if(!isset($args['attributes']['alt']) && isset($file['post_id'])) {
    $args['attributes']['alt'] = get_the_title($file['post_id']);
  }
}

Add the src attribute to the image

Last, but not least, I’m going to set the src attribute of the image and build my image tag.

function convert_post_meta_file_to_image($file, $args) {
  <!-- other stuff here -->
  $args['attributes']['src'] = $file_url;
  $attributes = convert_array_to_attribute_string($args['attributes']);
  return '<img ' . $attributes . ' />';
}

Hopefully this post provided some useful information on how images can be constrained via a function that can provide a fixed width or height.

Featured image by Jessica Ruscello.