<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package Imager::Transform;
use strict;
use Imager;
use Imager::Expr::Assem;
use vars qw($VERSION);

$VERSION = "1.005";

my %funcs =
  (
   mandel=&gt;
   {
    desc=&gt;"Mandelbrot set",
    type=&gt;'assem',
    assem=&gt;&lt;&lt;EOS,
# x treated as in range minx..maxx
# y treated as in range miny..maxy
    var nx:n ; var ny:n
    var diffx:n ; var diffy:n
# conx/y are x/y adjusted to min..max ranges
    var conx:n ; var cony:n
    diffx = subtract maxx minx
    conx = div x w
    conx = mult conx diffx
    conx = add conx minx
    diffy = subtract maxy miny
    cony = div y h
    cony = mult cony diffy
    cony = add cony miny
    nx = 0
    ny = 0
    var count:n
    count = 0
loop:
# calculate (nx,ny)**2 +(x,y)-&gt;
#  (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=&gt;
    {
     minx=&gt;{ default=&gt;-2, desc=&gt;'Left of rendered area', },
     miny=&gt;{ default=&gt;-1.5, desc=&gt;'Top of rendered area', },
     maxx=&gt;{ default=&gt;1, desc=&gt;'Right of rendered area', },
     maxy=&gt;{ default=&gt;1.5, desc=&gt;'Bottom of rendered area', },
     maxcount=&gt;{ default=&gt;100, desc=&gt;'Maximum iterations', },
     huestep=&gt;{ default=&gt;21.1, desc=&gt;'Hue step for number of iterations', },
     huebase=&gt;{ default=&gt;0, desc=&gt;'Base hue for number of iterations', },
     insideangle=&gt;
     { 
      default=&gt;0, 
      desc=&gt;'Non-zero to use angle of final as hue for inside',
     },
     insidebase=&gt;
     {
      default=&gt;0,
      desc=&gt;'Base angle for inside colours if insideangle is non-zero',
     },
     outsideangle=&gt;
     { 
      default=&gt;0, 
      desc=&gt;'Non-zero to use angle of final as hue for outside',
     },
     outsidebase=&gt;
     {
      default=&gt;0,
      desc=&gt;'Base angle if outsideangle is true',
     },
     outsidevalue=&gt;
     {
      default=&gt;1,
      desc=&gt;'Brightness for outside pixels',
     },
     outsidevaluestep=&gt;
     {
      default=&gt;0,
      desc=&gt;'Brightness step for each count for outside pixels',
     },
     outsidesat=&gt;
     {
      default=&gt;1,
      desc=&gt;'Saturation for outside pixels',
     },
    },
    inputs=&gt;[],
   },
   julia=&gt;
   {
    desc=&gt;"Julia set",
    type=&gt;'assem',
    assem=&gt;&lt;&lt;EOS,
#    print x
# x treated as in range minx..maxx
# y treated as in range miny..maxy
    var nx:n ; var ny:n
    var diffx:n ; var diffy:n
# conx/y are x/y adjusted to min..max ranges
    var conx:n ; var cony:n
    diffx = subtract maxx minx
    conx = div x w
    conx = mult conx diffx
    conx = add conx minx
    diffy = subtract maxy miny
    cony = div y h
    cony = mult cony diffy
    cony = add cony miny
    nx = conx
    ny = cony
    var count:n
    count = 0
loop:
# calculate (nx,ny)**2 +(x,y)-&gt;
#  (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=&gt;
    {
     zx=&gt;{default=&gt;0.7, desc=&gt;'Real part of initial Z', },
     zy=&gt;{default=&gt;0.2, desc=&gt;'Imaginary part of initial Z', },
     minx=&gt;{ default=&gt;-1.5, desc=&gt;'Left of rendered area', },
     miny=&gt;{ default=&gt;-1.5, desc=&gt;'Top of rendered area', },
     maxx=&gt;{ default=&gt;1.5, desc=&gt;'Right of rendered area', },
     maxy=&gt;{ default=&gt;1.5, desc=&gt;'Bottom of rendered area', },
     maxcount=&gt;{ default=&gt;100, desc=&gt;'Maximum iterations', },
     huestep=&gt;{ default=&gt;21.1, desc=&gt;'Hue step for number of iterations', },
     huebase=&gt;{ default=&gt;0, desc=&gt;'Base hue for number of iterations', },
     insideangle=&gt;
     { 
      default=&gt;0, 
      desc=&gt;'Non-zero to use angle of final as hue for inside',
     },
     insidebase=&gt;
     {
      default=&gt;0,
      desc=&gt;'Base angle for inside colours if insideangle is non-zero',
     },
     outsideangle=&gt;
     { 
      default=&gt;0, 
      desc=&gt;'Non-zero to use angle of final as hue for outside',
     },
     outsidebase=&gt;
     {
      default=&gt;0,
      desc=&gt;'Base angle if outsideangle is true',
     },
     outsidevalue=&gt;
     {
      default=&gt;1,
      desc=&gt;'Brightness for outside pixels',
     },
     outsidevaluestep=&gt;
     {
      default=&gt;0,
      desc=&gt;'Brightness step for each count for outside pixels',
     },
     outsidesat=&gt;
     {
      default=&gt;1,
      desc=&gt;'Saturation for outside pixels',
     },
    },
    inputs=&gt;[],
   },
   circleripple=&gt;
   {
    type=&gt;'rpnexpr',
    desc=&gt;'Adds a circular ripple effect',
    rpnexpr=&gt;&lt;&lt;'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=&gt;
    {
     freq=&gt; { desc=&gt;'Frequency of ripples', default=&gt;5 },
     depth=&gt; { desc=&gt;'Depth of ripples', default=&gt;10 },
     shadow=&gt; { desc=&gt;'Fraction of shadow', default=&gt;20 },
    },
    inputs=&gt;
	[
	 { desc=&gt;'Image to ripple' }
	 ],
   },
   spiral=&gt;
   {
    type=&gt;'rpnexpr',
    desc=&gt;'Render a colorful spiral',
    rpnexpr=&gt;&lt;&lt;'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=&gt;
    {
     spacing=&gt;{ desc=&gt;'Spacing between arms', default=&gt;10 },
    },
    inputs=&gt;[],
   },
   diagripple=&gt;
   {
    type=&gt;'rpnexpr',
    desc=&gt;'Adds diagonal ripples to an image',
    rpnexpr=&gt;&lt;&lt;'EOS',
x y + !dist @dist freq / sin !scale 
@scale depth * !adj
 x @adj + y @adj + getp1 @scale shadow + shadow 1 + / *
EOS
    constants=&gt; 
    {
     freq=&gt;{ desc=&gt;'Frequency of ripples', default=&gt;5, },
     depth=&gt;{desc=&gt;'Depth of ripples', default=&gt;3,},
     shadow=&gt;
     {
	 desc=&gt;'Fraction of brightness to remove for shadows',
	 default=&gt;20,
     },
    },
    inputs=&gt;
	[
	 { desc=&gt;'Image to add ripples to' }
	 ],
   },
   twist=&gt;
   {
    type=&gt;'rpnexpr',
    desc=&gt;'Twist an image',
    rpnexpr=&gt;&lt;&lt;'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=&gt;
    {
     twist=&gt;{ desc=&gt;'Amount of twist', default=&gt;2.5, },
    },
    inputs=&gt;
    [
     { desc=&gt;'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=&gt;$funcs{$name}, name=&gt;$name }, $class;
}

sub inputs {
  my ($self) = @_;
  return @{$self-&gt;{func}{inputs}}
}

sub constants {
  my $self = shift;
  if (@_) {
    return @{$self-&gt;{func}{constants}}{@_};
  }
  else {
    return keys %{$self-&gt;{func}{constants}};
  }
}

sub transform {
  my ($self, $opts, $constants, @in) = @_;

  my $func = $self-&gt;{func};
  my %opts = %$opts;
  $opts{$func-&gt;{type}} = $func-&gt;{$func-&gt;{type}};
  my %con = %$constants;
  for my $name (keys %{$func-&gt;{'constants'}}) {
    unless (exists $con{$name}) {
      if (exists $func-&gt;{'constants'}{$name}{default}) {
	$con{$name} = $func-&gt;{'constants'}{$name}{default};
      }
      else {
	$self-&gt;{error} = "No value or default for constant $name";
	return;
      }
    }
  }
  $opts{'constants'} = \%con;
  unless (@in == @{$func-&gt;{'inputs'}}) {
    $self-&gt;{error} = @in." input images given, ".
      @{$func-&gt;{'inputs'}}." supplied";
    return;
  }

  my $out = Imager::transform2(\%opts, @in);
  unless ($out) {
    $self-&gt;{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 &amp;&amp; !$name) {
    $func = $class-&gt;{func};
    $name = $class-&gt;{name}
  }
  else {
    $func = $funcs{$name}
      or return undef;
  }
  my $desc = &lt;&lt;EOS;
Function   : $name
Description: $func-&gt;{desc}
EOS
  if ($func-&gt;{'inputs'} &amp;&amp; @{$func-&gt;{'inputs'}}) {
    $desc .= "Input images:\n";
    my $i = 1;
    for my $in (@{$func-&gt;{'inputs'}}) {
      $desc .= "  $i: $in-&gt;{desc}\n";
    }
  }
  else {
    $desc .= "There are no input images\n";
  }
  if ($func-&gt;{'constants'} &amp;&amp; keys %{$func-&gt;{'constants'}}) {
    $desc .= "Input constants:\n";
    for my $key (keys %{$func-&gt;{'constants'}}) {
      $desc .= "  $key: $func-&gt;{'constants'}{$key}{desc}\n";
      $desc .= "       Default: $func-&gt;{'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-&gt;list;
  # create a transformation object
  my $tran = Imager::Transform-&gt;new($name);
  # describe it
  print $tran-&gt;describe;
  # a list of constant names
  my @constants = $tran-&gt;constants;
  # information about some of the constants
  my @info = $tran-&gt;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-&gt;list

Returns a list of the transformations.

=item my $desc = Imager::Transform-&gt;describe($name);

=item my $desc = $tran-&gt;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-&gt;new($name)

Create a new transformation object.  Returns undef if there is no such
transformation.

=item my @inputs = $tran-&gt;inputs;

=item my $inputs = $tran-&gt;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&lt;desc&gt;, a description of the use of the input image.

=item $tran-&gt;constants

Returns a list of names of constants that can be set for the
transformation.

=item $tran-&gt;constants($name, $name, ...)

Returns a hashref for each named constant, which contains the default
in key C&lt;default&gt; and a description in key C&lt;desc&gt;.

=item my $out = $tran-&gt;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-&gt;errstr to get an error message.

=item $tran-&gt;errstr

The error message, if any from the last image transformation.

=back

=head1 BUGS

Needs more transformations.

=head1 SEE ALSO

Imager(3), F&lt;transform.perl&gt;

=cut
</pre></body></html>