views:

57

answers:

3

I have a dynamic thumbnail script I found laying around the web and tweaked a bit. One of the things I added was a caching mechanism. Whenever a new thumb is generated, it is saved to disk, and the disk copy will be used if the same thumbnail (with all the same options) is requested again.

A snippet:

  // name of cached file
  $thumb_file = $_SERVER['DOCUMENT_ROOT'].'/thumbs/cache/'.
                str_replace('/', '_', $_REQUEST['p']).
                ".{$def_width}x{$def_height}".
                ($clamp ? '_'.implode('x',$clamp) : '').
                ($make_png?'.png':'.jpg');

  // get it from cache if it's there
  if ($use_cache && file_exists($thumb_file)) {
    Header("Content-type: image/".($make_png?'png':'jpeg'));

    // this part seems really slow

    $fp=fopen($thumb_file, "rb");
    while (!feof($fp)) print fread($fp, 4096);

    exit();
  }

However, printing the result of fread seems to be very slow, and sometimes (very rarely) the images don't load completely.

So, how can I speed this up? Should I just redirect the browser to the image instead of freading it, or is there another option?

I'm including the full PHP script below, just in case.

<?php

  $use_cache = $_REQUEST['nc'] ? false : true;
  // $use_cache = false;

  $upfile = $_SERVER['DOCUMENT_ROOT'] .'/'. $_REQUEST['p'];
  $def_width  = $_REQUEST["w"];
  $def_height = $_REQUEST["h"];
  $clamp = $_REQUEST['c'] ? explode("x",$_REQUEST['c']) : null;
  $make_png = $_REQUEST['png'];

  if (!file_exists($upfile)) {
    die();  // $upfile = "nophoto.jpg";
  }

  if (!"{$def_width}{$def_height}") {
    $def_width = $def_height = '100';
  }

  // name of cached file
  $thumb_file = $_SERVER['DOCUMENT_ROOT'].'/thumbs/cache/'.
                str_replace('/', '_', $_REQUEST['p']).
                ".{$def_width}x{$def_height}".
                ($clamp ? '_'.implode('x',$clamp) : '').
                ($make_png?'.png':'.jpg');

  // get it from cache if it's there
  if ($use_cache && file_exists($thumb_file)) {
    Header("Content-type: image/".($make_png?'png':'jpeg'));
    $fp=fopen($thumb_file, "rb");
    while (!feof($fp)) print fread($fp, 4096);
    exit();
  }

  $ext = strtolower(substr($upfile, -3));

  ini_set('memory_limit', '64M');

  if ($ext=="gif") 
    $src = @ImageCreateFromGif ($upfile);
  else if ($ext=="jpg") 
    $src = @ImageCreateFromJpeg($upfile);
  else if ($ext=="png") 
    $src = @ImageCreateFromPng($upfile);

  $size = GetImageSize($upfile); 
  $width = $size[0];
  $height = $size[1];

  $long_side = $def_width;
  if ($def_width < $def_height) $long_side = $def_height;

  if (!$def_width) {
    $factor_h = $height / $def_height;
    $def_width = $width / $factor_h;
  }
  if (!$def_height) {
    $factor_w = $width / $def_width;
    $def_height = $height / $factor_w;
  }
  $factor_w = $width / $def_width;
  $factor_h = $height / $def_height;

  if ($factor_w > $factor_h) {
    $new_height = floor($def_height * $factor_h);
    $new_width = floor($def_width  * $factor_h);
  } else {
    $new_height = floor($def_height * $factor_w);
    $new_width = floor($def_width  * $factor_w);
  }

  if ((!$clamp[0])&&$clamp[0]!=='0') $clamp[0] = 50;
  if ((!$clamp[1])&&$clamp[1]!=='0') $clamp[1] = 50;

  $src_x = ceil(($width  - $new_width)  * ($clamp[0] / 100));
  $src_y = ceil(($height - $new_height) * ($clamp[1] / 100));

  $dst = ImageCreateTrueColor($def_width, $def_height);

  @ImageCopyResampled($dst, $src, 0, 0, $src_x, $src_y, 
                      $def_width, $def_height, $new_width, $new_height);

  Header("Content-type: image/".($make_png?'png':'jpeg'));

  if ($make_png) {
    ImagePng($dst);
    if ($use_cache) { 
      ImagePng($dst, $thumb_file); 
    }
  } else {
    ImageJpeg($dst, null, 95);
    if ($use_cache) { 
      ImageJpeg($dst, $thumb_file, 95); 
    }
  }

  @ImageDestroy($src);
  @ImageDestroy($dst);

?>
+1  A: 

The function readfile should be faster.

If you are using PHP as an Apache module, you can also look into virtual.

Artefacto
Wow nearly simultaneous posts.
NullUserException
@Null Both say 2010-08-15 02:13:45Z. We'd need photo finish here :p
Artefacto
A: 

If your web server supports it, X-Sendfile might help speed things up.

A: 

Please define "seems" and "very slow" in terms of certain numbers.
Otherwise there can be answers only in the same terms - "it seems you can roughly get something".

Anyone who is asking "How can I speed up" should answer to these questions first:

  • what certain operation I want to speed up
  • what certain amount of time it takes at the moment.
  • what certain amount of time will suit me.

According to my own not-so-perfect tests, serving pictures via PHP is 2 times slower than via web-server itself. Reasonable I'd say, not "very slow".
So, if it runs "very slow" there could be some other reasons. Like this caching mechanism is not working at all due to some mistake. Or something. Some profiling, as well as debugging is required before anyone can help.

Especially for ones who will squeak "This is not an answer!" (because of that silly assumption that answer counts only it it's direct and positive), here is a link for the OP to learn from:
http://shiftingpixel.com/2008/03/03/smart-image-resizer/
a same on the fly thumbnail creation script but with " Not Modified" HTTP cache implementation.

Col. Shrapnel
It's slow enough to annoy me into inquiring about it on SO. I suppose I could test it with javascript or wireshark or something, but I was hoping this was a common enough problem that someone else already knew the answer. Incidentally I think you're on the right track with the cache headers; thanks for the link.
no
304 not modified did the trick. Thanks.
no
@no it is not a common problem. The cure helped but you still do not know disease
Col. Shrapnel
@Col. Shrapnel - I suspect it might have had something to do with the content-length header not being there. I really don't have a lot of time to spend on a small issue like this; using the browser cache via the 304 thing is good enough for me.
no