package Imager::Transform; use strict; use Imager; use Imager::Expr::Assem; use vars qw($VERSION); $VERSION = "1.006"; my %funcs = ( mandel=> { desc=>"Mandelbrot set", type=>'assem', assem=>< # (nx*nx-ny*ny+x, 2.nx.ny+y) var wx:n ; var wy:n ; var work:n wx = mult nx nx wy = mult ny ny wx = subtract wx wy ny = mult ny nx ny = mult ny 2 nx = wx nx = add nx conx ny = add ny cony work = distance nx ny 0 0 work = gt work 2 jumpnz work docol count = add count 1 work = lt count maxcount jumpnz work loop jumpnz insideangle doinang var workp:p workp = rgb 0 0 0 ret workp doinang: var ang:n ang = atan2 ny nx ang = mult ang 360 ang = div ang pi workp = hsv ang 255 0.5 ret workp docol: var outvalue:n outvalue = mult outsidevaluestep count outvalue = add outvalue outsidevalue outvalue = mod outvalue 1.01 jumpnz outsideangle do_outang work = mult count huestep work = add work huebase work = mod work 360 workp = hsv work 1 outvalue ret workp do_outang: ang = atan2 ny nx ang = mult ang 360 ang = div ang pi ang = add ang outsidebase workp = hsv ang outsidesat outvalue ret workp EOS constants=> { minx=>{ default=>-2, desc=>'Left of rendered area', }, miny=>{ default=>-1.5, desc=>'Top of rendered area', }, maxx=>{ default=>1, desc=>'Right of rendered area', }, maxy=>{ default=>1.5, desc=>'Bottom of rendered area', }, maxcount=>{ default=>100, desc=>'Maximum iterations', }, huestep=>{ default=>21.1, desc=>'Hue step for number of iterations', }, huebase=>{ default=>0, desc=>'Base hue for number of iterations', }, insideangle=> { default=>0, desc=>'Non-zero to use angle of final as hue for inside', }, insidebase=> { default=>0, desc=>'Base angle for inside colours if insideangle is non-zero', }, outsideangle=> { default=>0, desc=>'Non-zero to use angle of final as hue for outside', }, outsidebase=> { default=>0, desc=>'Base angle if outsideangle is true', }, outsidevalue=> { default=>1, desc=>'Brightness for outside pixels', }, outsidevaluestep=> { default=>0, desc=>'Brightness step for each count for outside pixels', }, outsidesat=> { default=>1, desc=>'Saturation for outside pixels', }, }, inputs=>[], }, julia=> { desc=>"Julia set", type=>'assem', assem=>< # (nx*nx-ny*ny+x, 2.nx.ny+y) var wx:n ; var wy:n ; var work:n wx = mult nx nx wy = mult ny ny wx = subtract wx wy ny = mult ny nx ny = mult ny 2 nx = wx nx = add nx zx ny = add ny zy work = distance nx ny 0 0 work = gt work 2 jumpnz work docol count = add count 1 work = lt count maxcount jumpnz work loop jumpnz insideangle doinang var workp:p workp = rgb 0 0 0 ret workp doinang: var ang:n ang = atan2 ny nx ang = mult ang 360 ang = div ang pi workp = hsv ang 255 0.5 ret workp docol: var outvalue:n outvalue = mult outsidevaluestep count outvalue = add outvalue outsidevalue outvalue = mod outvalue 1.01 jumpnz outsideangle do_outang work = mult count huestep work = add work huebase work = mod work 360 workp = hsv work 1 outvalue ret workp do_outang: ang = atan2 ny nx ang = mult ang 360 ang = div ang pi ang = add ang outsidebase workp = hsv ang outsidesat outvalue ret workp EOS constants=> { zx=>{default=>0.7, desc=>'Real part of initial Z', }, zy=>{default=>0.2, desc=>'Imaginary part of initial Z', }, minx=>{ default=>-1.5, desc=>'Left of rendered area', }, miny=>{ default=>-1.5, desc=>'Top of rendered area', }, maxx=>{ default=>1.5, desc=>'Right of rendered area', }, maxy=>{ default=>1.5, desc=>'Bottom of rendered area', }, maxcount=>{ default=>100, desc=>'Maximum iterations', }, huestep=>{ default=>21.1, desc=>'Hue step for number of iterations', }, huebase=>{ default=>0, desc=>'Base hue for number of iterations', }, insideangle=> { default=>0, desc=>'Non-zero to use angle of final as hue for inside', }, insidebase=> { default=>0, desc=>'Base angle for inside colours if insideangle is non-zero', }, outsideangle=> { default=>0, desc=>'Non-zero to use angle of final as hue for outside', }, outsidebase=> { default=>0, desc=>'Base angle if outsideangle is true', }, outsidevalue=> { default=>1, desc=>'Brightness for outside pixels', }, outsidevaluestep=> { default=>0, desc=>'Brightness step for each count for outside pixels', }, outsidesat=> { default=>1, desc=>'Saturation for outside pixels', }, }, inputs=>[], }, circleripple=> { type=>'rpnexpr', desc=>'Adds a circular ripple effect', rpnexpr=><<'EOS', x y cx cy distance !dist @dist freq / sin !scale @scale depth * @dist + !adj y cy - x cx - atan2 !ang cx @ang cos @adj * + cy @ang sin @adj * + getp1 @scale shadow + shadow 1 + / * EOS constants=> { freq=> { desc=>'Frequency of ripples', default=>5 }, depth=> { desc=>'Depth of ripples', default=>10 }, shadow=> { desc=>'Fraction of shadow', default=>20 }, }, inputs=> [ { desc=>'Image to ripple' } ], }, spiral=> { type=>'rpnexpr', desc=>'Render a colorful spiral', rpnexpr=><<'EOS', x y cx cy distance !d y cy - x cx - atan2 !a @d spacing / @a + pi 2 * % !a2 @a 180 * pi / 1 @a2 sin 1 + 2 / hsv EOS constants=> { spacing=>{ desc=>'Spacing between arms', default=>10 }, }, inputs=>[], }, diagripple=> { type=>'rpnexpr', desc=>'Adds diagonal ripples to an image', rpnexpr=><<'EOS', x y + !dist @dist freq / sin !scale @scale depth * !adj x @adj + y @adj + getp1 @scale shadow + shadow 1 + / * EOS constants=> { freq=>{ desc=>'Frequency of ripples', default=>5, }, depth=>{desc=>'Depth of ripples', default=>3,}, shadow=> { desc=>'Fraction of brightness to remove for shadows', default=>20, }, }, inputs=> [ { desc=>'Image to add ripples to' } ], }, twist=> { type=>'rpnexpr', desc=>'Twist an image', rpnexpr=><<'EOS', x y cx cy distance !dist y cy - x cx - atan2 @dist twist / + !ang cx @ang cos @dist * + cy @ang sin @dist * + getp1 EOS constants=> { twist=>{ desc=>'Amount of twist', default=>2.5, }, }, inputs=> [ { desc=>'Image to twist' }, ], }, # any other functions can wait until Imager::Expr::Infix supports # jumps ); sub new { my ($class, $name) = @_; exists $funcs{$name} or return; bless { func=>$funcs{$name}, name=>$name }, $class; } sub inputs { my ($self) = @_; return @{$self->{func}{inputs}} } sub constants { my $self = shift; if (@_) { return @{$self->{func}{constants}}{@_}; } else { return keys %{$self->{func}{constants}}; } } sub transform { my ($self, $opts, $constants, @in) = @_; my $func = $self->{func}; my %opts = %$opts; $opts{$func->{type}} = $func->{$func->{type}}; my %con = %$constants; for my $name (keys %{$func->{'constants'}}) { unless (exists $con{$name}) { if (exists $func->{'constants'}{$name}{default}) { $con{$name} = $func->{'constants'}{$name}{default}; } else { $self->{error} = "No value or default for constant $name"; return; } } } $opts{'constants'} = \%con; unless (@in == @{$func->{'inputs'}}) { $self->{error} = @in." input images given, ". @{$func->{'inputs'}}." supplied"; return; } my $out = Imager::transform2(\%opts, @in); unless ($out) { $self->{error} = $Imager::ERRSTR; return; } return $out; } sub errstr { return $_[0]{error}; } sub list { return keys %funcs; } sub describe { my ($class, $name) = @_; my $func; if (ref $class && !$name) { $func = $class->{func}; $name = $class->{name} } else { $func = $funcs{$name} or return undef; } my $desc = <{desc} EOS if ($func->{'inputs'} && @{$func->{'inputs'}}) { $desc .= "Input images:\n"; my $i = 1; for my $in (@{$func->{'inputs'}}) { $desc .= " $i: $in->{desc}\n"; } } else { $desc .= "There are no input images\n"; } if ($func->{'constants'} && keys %{$func->{'constants'}}) { $desc .= "Input constants:\n"; for my $key (keys %{$func->{'constants'}}) { $desc .= " $key: $func->{'constants'}{$key}{desc}\n"; $desc .= " Default: $func->{'constants'}{$key}{default}\n"; } } else { $desc .= "There are no constants\n"; } return $desc; } 1; __END__ =head1 NAME Imager::Transform - a library of register machine image transformations =head1 SYNOPSIS # get a list of transformations my @funcs = Imager::Transform->list; # create a transformation object my $tran = Imager::Transform->new($name); # describe it print $tran->describe; # a list of constant names my @constants = $tran->constants; # information about some of the constants my @info = $tran->constants(@constants); =head1 DESCRIPTION This module provides a library of transformations that use the Imager transform2() function. The aim is to provide a place to collect these transformations. At some point there might be an interface to add new functions, but there's not a whole lot of point to that. The interface is a little sparse as yet. =head1 METHODS =over 4 =item my @names = Imager::Transform->list Returns a list of the transformations. =item my $desc = Imager::Transform->describe($name); =item my $desc = $tran->describe() Describes a transformation specified either by name (as a class method) or by reference (as an instance method). The class method returns undef if there is no such transformation. =item my $tran = Imager::Transform->new($name) Create a new transformation object. Returns undef if there is no such transformation. =item my @inputs = $tran->inputs; =item my $inputs = $tran->inputs; Returns a list of input image descriptions, or the number of them, depending on content. The list contains hash references, which current contain only one member, C, a description of the use of the input image. =item $tran->constants Returns a list of names of constants that can be set for the transformation. =item $tran->constants($name, $name, ...) Returns a hashref for each named constant, which contains the default in key C and a description in key C. =item my $out = $tran->transform(\%opts, \%constants, @imgs) Perform the image transformation. Returns the new image on success, or undef on failure, in which case you can use $tran->errstr to get an error message. =item $tran->errstr The error message, if any from the last image transformation. =back =head1 BUGS Needs more transformations. =head1 SEE ALSO Imager(3), F =head1 AUTHOR Tony Cook =cut