NAME

Catalyst::Controller::Imager - generate scaled or mangled images

VERSION

version 0.06

SYNOPSIS

# use the helper to create your Controller
script/myapp_create.pl controller Image Imager

# DONE. READY FOR USE.



# Just use it in your template:
# will deliver a 200 pixel wide version of some_image.png as jpg
<img src="/image/w-200/some_image.png.jpg" />

# will deliver a 210 by 300 pixel sized image without conversion
# (empty areas will be white)
<img src="/image/w-210-h-300/other_image.jpg" />

# will deliver a 80 by 80 pixel sized image
# (empty areas will be white)
<img src="/image/thumbnail/other_image.jpg" />

# define a modifier of your own
<img src="/image/blur-9/some_image.png.jpg" />

# your modifier plus a predefined one
<img src="/image/thumbnail-blur-9/some_image.png.jpg" />

# same thing as above
<img src="/image/blur-9-thumbnail/some_image.png.jpg" />



# in your Controller you then need:
sub want_blur :Action :Args(1) {
    ### do something to get a blurred image
}

DESCRIPTION

A Catalyst Controller that generates image files in any size you request and optionally converts the image format. Images are taken from a cache directory if possible and desired or generated on the fly. The Cache-directory has a structure that is very similar to the URI scheme, so a redirect rule in your webserver's setup would do this job also.

The URI of an image consists of always the same parts:

the action namespace

If your Controller is named MyApp::Controller::Image, this first part will be image.

modifier(s)

Here a series of modifiers and arguments separated with single dashes ('-') are used.

h-100        # will request an image's height
w-200        # will request an image's width
h-80-w-20    # both, height and width will apply
thumbnail    # a configurable square (defaults to 80)
image path

This is the relative path to the image that should get rendered

extension (optional)

If an additional option like .gif is added immediately after the image path, this format is requested for delivery.

A Controller that is derived from Catalyst::Controller::Imager may define its own modifier functions. See EXTENDING below.

Possible initially defined options are:

w-n

specifies the width of the image to generate. The height is adjusted to maintain the same ratio as the original image. The maximum size is controlled by a configuration parameter max_size that defaults to 1000.

Can be used in conjunction with h-n. However, if both options are given, the image will scale to fill the given area either by width or by height, get centered inside the area and additional spaces will get filled with white.

h-n

specifies the height of the image to generate. The width is adjusted to maintain the same ratio as the original image. The maximum size is controlled by a configuration parameter max_size that defaults to 1000.

Can be used in conjunction with w-n. However, if both options are given, the image will scale to fill the given area either by width or by height, get centered inside the area and additional spaces will get filled with white.

thumbnail

requests the generation of a thumbnail image. Defaults to a maximum size of thumbnail_size. The size can get changed by a simple configuration parameter.

Configuration

A simple configuration of your Controller could look like this:

__PACKAGE__->config(
    # the directory to look for files (inside root)
    # defaults to 'static/images'
    root_dir => 'static/images',
    
    # specify a cache dir if caching is wanted
    # defaults to no caching (more expensive)
    cache_dir => undef,
    
    # specify a format that will get delivered if
    # not guessable from the file extension
    # defaults to 'jpg'
    default_format => 'jpg'
    
    # specify a maximum value for width and height of images
    # defaults to 1000 pixels
    max_size => 1000,
            
    # specify the size of thumbnails (always square)
    # defaults to 80 pixels
    thumbnail_size => 80,

    # set jpeg quality
    # defaults to 95
    jpeg_quality => 95,
);

Caching

If caching is enabled (by setting the cache_dir configuration parameter), every image rendered will get saved into the cache directory if it exists and the directory is writable.

The path of a cached image inside the cache directory is identical to the URI part after the action namespace. Thus, a properly configured webserver might take over the responsibility to deliver static images from cache removing the burden from your Catalyst Controller.

INTERNALS

This base class defines a Chained dispatch chain consisting of the following Action methods. Each method is responsible for eating up a defined part of the URI. The URI always consists of 3 parts: The namespace, a format and size modifier and a relative path to the image in question optionally with another file extension added for format conversion.

Action Chain

To allow easy modification the URI dispatching is left to Catalyst. The following :Chained actions each work on a stage of the image construction. The final image will get delivered by the end action.

base

consumes the namespace of the controller inheriting this one.

scale

consumes a single URI part. If the part is a concatenation of several things joined with a dash '-', then these things are regarded as either arguments to an action or further actions with their arguments.

If a modifier is named 'blur' and needs a single parameter, you may define a method like:

sub blur :Action :Args(1) {
    # do something to blur
}

During this stage, the only thing that happens is recording every modification into a series of stash-variables.

image

The final stage consumes the image path and tries to find the image in question.

After the image is found, a forward to 'generate_image' is issued which does the conversion we want.

Stash Variables

All action methods communicate with each other by setting or retrieving stash variables.

image_path

relative path to original image

image

Imager Object as soon as image is loaded

image_data

binary image data after conversion or from cache

cache_path

relative path to cached image

format

format for conversion

scale

{ w => n, h => n, mode => min/max/fit/fill }

before_scale

list of Actions executed before scaling ### FIXME: action or subref???

after_scale

list of Actions executed after scaling ### FIXME: action or subref???

EXTENDING

The magic behind all the conversions is the existence of specially named action methods (their name starts with 'want_' or 'scale_').

want_

Actions starting with 'want_' get triggered if the URI part after the package namespace contains a word that matches the remainder of the action's name. The :Arg() attribute specifies how many additional parts this action will need for its operation.

scale_

One part of the scaling hash inside stash is a scaling mode. Depending on the name of the scaling mode, an action named 'scale_mode' is used to process the scaling.

If you plan to offer URIs like:

/image/small/image.jpg
/image/size-200-300/image.jpg
/image/watermark/image.jpg

# or a combination of them:
/image/size-200-300-watermark/image.jpg

# but not invalid things:
/image/size-200/image.jpg

you may build these action methods:

sub want_small :Action :Args(0) {
    my ($self, $c) = @_;
    
    $c->stash(scale => {w => 200, h => 200, mode => 'fill'});
}

sub want_size :Action :Args(2) {
    my ($self, $c, $w, $h) = @_;
    
    $c->stash(scale => {w => $w, h => $h, mode => 'fill'});
}

sub want_watermark :Action :Args(0) {
    my ($self, $c) = @_;
    
    ### FIXME: action or subref???
    push @{$c->stash->{after_scale}}, \&watermark_generator;
}

METHODS

BUILD

base :Chained :PathPrefix :CaptureArgs(0)

start of the action chain -- eats package namespace, eg. /image

scale :Chained('base') :PathPart('') :CaptureArgs(1)

second chain step -- eat up modifier parameter(s)

Modifier parameters and their args must be separated by single dashes ('-'). For every modifier there must exist an action named want_modifiername declared with the number of args it wants to consume

# modifier 'h', one argument
# eg h-200
sub want_h :Action :Arg(1)

# modifier 'size, two arguments
# eg size-300-200
sub want_size :Action :Arg(2)

image :Chained('scale') :PathPart('') :Args

final chain step --consumes image path relative to root_dir plus optional format extension for conversion

convert_image :Action

The converting function. Consumes all conversion-relevant parameters from stash and does the conversion (or delivers a file from stash).

end :Action

deliver the data or fire a 404-status in case something went wrong.

Yes, I know, a 404 means 'not found', but for the end-user there is no difference between a not found image and an error that occured. And basically if somebody puts rubbush into the URL and calling an unknown action internally is a Internal server error, but for the end-user the requested image and its modification could not get retrieved.

scale_min :Action

scales an image by the minimum scaling factor needed to either match the desired width or height.

scale_max :Action

scales an image by the maximum scaling factor needed to either match the desired width or height.

scale_fit :Action

first scales an image by the maximum scaling factor needed to either match the desired width or height. Then, crops the image to make it fit the desired size.

scale_fill :Action

scales an image by the minimum scaling factor needed to either match the desired width or height. Then, expand the image with white color to make it fit the desired size.

want_thumbnail :Action :Args(0)

Logic for the 'thumbnail' modifier without further args. Sets the requested width and height to the thumbnail_size configuration parameter (default is 80).

want_w :Action :Args(1)

Logic for the 'w' modifier with one arg. Sets the requested width of the image.

want_h :Action :Args(1)

Logic for the 'h' modifier with one arg. Sets the requested height of the image.

BUGS

probably many... Don't get confused if tests fail and carefully read the messages. The test-suite only will pass if Imager is configured with gif, jpeg and png support. In doubt install the required binary libraries and reinstall Imager.

AUTHOR

Wolfgang Kinkeldei, <wolfgang@kinkeldei.de>

LICENSE

This library is free software, you can redistribute it and/or modify it under the same terms as Perl itself.