/usr/local/CPAN/HTML-Bricks/HTML/Bricks/Magick.pm


package HTML::Bricks::Magick;

use strict;
use Apache::Constants qw(:common);
use Apache::File ();
use File::Basename qw(fileparse);
use DirHandle ();

our $VERSION = '0.02';

my %LegalArguments = map { $_ => 1 } 
qw (adjoin background bordercolor colormap colorspace
    colors compress density dispose delay dither
    display font format iterations interlace
    loop magick mattecolor monochrome page pointsize
    preview_type quality scene subimage subrange
    size tile texture treedepth undercolor);

my %LegalFilters = map { $_ => 1 } 
qw(AddNoise Blur Border Charcoal Chop
   Contrast Crop Colorize Comment CycleColormap
   Despeckle Draw Edge Emboss Enhance Equalize Flip Flop
   Frame Gamma Implode Label Layer Magnify Map Minify
   Modulate Negate Normalize OilPaint Opaque Quantize
   Raise ReduceNoise Resize Rotate Sample Scale Segment Shade
   Sharpen Shear Solarize Spread Swirl Texture Transparent
   Threshold Trim Wave Zoom);

sub handler {
    use Image::Magick;

    my $r = shift;

    return DECLINED unless $r->filename =~ /.*\.[jJ][pP][eE]?[gG]/;

    # get the name of the requested file
    my $file = $r->filename;

    # If the file exists and there are no transformation arguments
    # just decline the transaction.  It will be handled as usual.
    return DECLINED unless $r->args || $r->path_info || !-r $r->finfo;
    
    my $source;
    my ($base, $directory, $extension) = fileparse($file, '\.\w+');
    if (-r $r->finfo) { # file exists, so it becomes the source
  	$source = $file;
    } 
    else {              # file doesn't exist, so we search for it
  	return DECLINED unless -r $directory;
  	$source = find_image($r, $directory, $base);
    }
    
    unless ($source) {
  	$r->log_error("Couldn't find an image for $file");
  	return NOT_FOUND;
    }
  
    # Get args and construct our cached URI

    my $args = $r->args;
    my $cached = $r->document_root .  '/.magick_cache' . $r->uri . '?' . $args;

    # If the filtered image is already cached on the server, and the mtimes
    # match (indicating that the file hasn't been updated since the cached 
    # copy was created (see the bottom of this sub)) send the cached copy.

    my $mtime_source = ${@{[stat($source)]}}[9];

    #
    # but first check for clients polling to see if the file's been modified
    #

    if ($r->header_only) {

      if (-e $cached) {
        my $mtime_cached = ${@{[stat($cached)]}}[9];
        if ($mtime_cached == $mtime_source) {
          $r->update_mtime($mtime_cached);
          $r->set_last_modified;
        }
      }
      $r->send_http_header;
      return OK;
    }

    if (-e $cached) {
        my $mtime_cached = ${@{[stat($cached)]}}[9];
        if ($mtime_cached == $mtime_source) {
          my $fh = Apache::gensym();
          open ($fh, $cached) || return NOT_FOUND;
          $r->send_fd($fh);
          close($fh);
          return OK;
	} 
    }
   
    # Read the image
    my $q = Image::Magick->new;
    my $err = $q->Read($source);

    # Conversion arguments and image filter operations are kept in 
    # the query string.

    my %arguments;
    my @filters = split("&",$args);

    foreach (@filters) {
        my @fields = split(':', $_);
	my $filter = ucfirst shift @fields;
	next unless $LegalFilters{$filter};

        foreach (@fields) {
          my @arg = split('=',$_);
          $arguments{$arg[0]} = $arg[1];
        }
	$err ||= $q->$filter(%arguments);    # apply filters one at a time
    }

    # Remove invalid arguments before the conversion
    foreach (keys %arguments) { 	
	delete $arguments{$_} unless $LegalArguments{$_};
    }

    my ($tmpnam, $fh) = Apache::File->tmpfile;
    
    # Write out the modified image
    open(STDOUT, ">&=" . fileno($fh));
    $extension =~ s/^\.//;
    $err ||= $q->Write('filename' => "\U$extension\L:-", %arguments);
    if ($err) {
  	unlink $tmpnam;
  	$r->log_error($err);
  	return SERVER_ERROR;
    }
    close $fh;
    
    # At this point the conversion is all done!
    # reopen for reading
    $fh = Apache::File->new($tmpnam);
    unless ($fh) {
  	$r->log_error("Couldn't open $tmpnam: $!");
  	return SERVER_ERROR;
    }
    
    # send the file
    $r->send_fd($fh);
    
    # create the directory to put the cached file if it doesn't exist

    my $dir = substr($cached,0,rindex($cached,"/"));    

    if (! -e $dir) {
      # see if we can create the directory

      my $dir = $r->document_root . "/.magick_cache";
      my @path = split("/",$r->uri);

      foreach(@path) {
        mkdir $dir;
        $dir .= "/$_";
      } 
    }

    # save the file to the cache, set the mtime and return
 
    use File::Copy;
    move($tmpnam,$cached);
    utime(time,$mtime_source,$cached);

    return OK;
}

sub find_image {
    my ($r, $directory, $base) = @_;
    my $dh = DirHandle->new($directory) or return;

    my $source;
    for my $entry ($dh->read) {
  	my $candidate = fileparse($entry, '\.\w+');
  	if ($base eq $candidate) {
  	    # determine whether this is an image file
	    $source = join '', $directory, $entry;
  	    my $subr = $r->lookup_file($source);
  	    last if $subr->content_type =~ m:^image/:;
	    $source = "";
  	}
    }
    $dh->close;
    return $source;
}

1;
__END__