Faking imagecache for external images in Drupal

Imagecache is just one of those modules that we use on a consistent basis. There are usually many places within a complex site where the ability to crop and scale an image to a certain size comes into play. While this works great for images that you are storing on your own server, you run into problems when you want to specify an image on an external site or server. Fortunately in this case, we can use a little CSS/XHTML to simulate imagecache’s dimension presets and manipulate how an external image is displayed on your site.

At the time of writing this, imagecache doesn’t yet allow images on a different site/server other than your own to be processed by presets. There are other ways to accomplish the following, most notably using jQuery… or eliminating the need for external images altogether by having them scraped from the external site and stored on your own server… but this blog just focuses on a CSS/XHTML solution to the issue.

The hypothetical situation:

You are working on a node template… let’s say a “news” template (node-news.tpl.php). You need to display an image that is actually hosted on a different site, for example, Photobucket. You’ve added a text field to your news content type called “External URL”, which allows you to specify the URL of an featured image you want to be associated with your news story.

UPDATE: Drupal security team member greggles has just tipped me off that just creating a regular old field to use for an external URL is a big security no-no. The best approach to doing this is to use the Link module to collect your external URL since the module will make sure that it is really a URL being entered in the field and not some malicious code that can blow your site up or use it for some nefarious purpose.

Regardless, this solution requires testing and should only be used for content types and/or fields that are created by your editors or other trusted users, to ensure that you are not being opened up to an attack.

Check out his new book, Cracking Drupal… all the cool kids are doing it.

In your node template, you can print the image by using:

<img src="field_external_url['0']['safe'] ?>" />

Your code may differ depending on what you named the field. dpm($node) is your friend when trying to find certain things within your node object.

Note the ['safe'] part of that code. What you’re aiming to use here is the “safe” version of the data that the Link module will supply you in the node object. Check out http://drupal.org/writing-secure-code and make sure that if you’re not using the Link module to collect a URL, that you run your user-supplied content (in this case a URL, hopefully) through the appropriate filters to prevent bad things from happening.

This will print the image at the full size it appears on the external site. If it’s a large external image, this original size may not suit your needs and you’ll need to downsize it. Since imagecache presets can’t be used in this situation, we’ll need to fake it with some CSS and a little extra markup. If you are familiar with the process of using “Sliding Doors” for CSS rollovers you will find the following somewhat familiar. If you’re not familiar with that, you should give the article that started it all a read. It’s not particularly applicable in this scenario, but the concept is very similar.

The scale:

This one’s pretty obvious. Use the “width” and “height” CSS attributes to scale the image to how you need it to be.


.image img {
width:200px;
}

You’ll notice that the height is automatically changed as well on the image, so that it maintains its proportion as it relates to an aspect ratio.

BEFORE:

AFTER:

The crop:

OK, this one’s a little more tricky. The idea here is to use an outer div to act as a “frame” that holds the image, and only shows a certain portion of the image. Here’s a basic example:

<img src="field_external_url['0']['value'] ?>" />

You want your frame div to be of a certain set size, heightwise and widthwise, to contain your image. You also want to use overflow:auto so that any part of the image that falls outside your set frame size is not visible.


.frame {
width: 200px;
height: 100px;
overflow:hidden;
}

This will place your image using the top left corner of your frame as a registration point. If you want the the image to be aligned to the upper right side of the frame instead, you can float:right the image. But say you don’t want to show the upper left or right portion of the image? Well, there is a way to “shift” the image around inside the frame so that the part you want to show is showing through.

By using negative margins on the image itself, we can tug at it in the direction we want it to slide around in the frame.


.image img {
margin-left:-50px;
margin-top:-50px;
}
.frame {
width: 200px;
height: 100px;
overflow:hidden;
}

The margin-left:-50px here will slide the image to the left, revealing more of the right. The margin-top:-50px here will slide the image to the top, revealing more of the bottom. If you want real control over what each image and frame is showing, you’ll have to do this process for individual images and tweak the margins as necessary.

The scale/crop:

The scale and crop, is just that, a combination of both of these techniques. In this example, the width of the image has been set to scale to the size of the frame. This means you would only have to adjust the negative margin-top to shift it up or down under the frame.


.image img {
width:200px;
margin-top:-20px;
}
.frame {
width: 200px;
height: 100px;
overflow:hidden;
}

Issues with this technique

There are a few hiccups to discuss, including of course your obligatory IE6 problems.

  • Hardcoded width/height values: Notice the lack of height or width attributes on the img tag itself… this example only really works if your img tag doesn’t have them. After all, why would you want them if you’re going to be altering the dimensions with CSS? If for some reason, you have to use a img tag that has it’s own width and height attributes hardcoded, then the workaround is to first have a bit of jQuery strip off those attributes from your image. Another issue with hardcoded height/width values is that if you do have them, IE6 will refuse to obey your width:auto or height:auto CSS style rules, if you have them.
  • Upscaling: While I find upscaling an image to be a terrible decision most of the time, I suppose there may be some cases where you need to take an external image that is small and display it larger on your site. Using height and width will also upscale a smaller image to the size you need it to be.