[rt #99959] fix Imager::Matrix2d::rotate()'s centre point hanling
authorTony Cook <tony@develop-help.com>
Sat, 15 Nov 2014 02:47:48 +0000 (13:47 +1100)
committerTony Cook <tony@develop-help.com>
Sat, 15 Nov 2014 02:47:48 +0000 (13:47 +1100)
also add a compose() method and add notes on the order of multiplcation
vs composition of transformations.

lib/Imager/Matrix2d.pm
t/900-util/050-matrix.t

index a8ba80d..3aa2cf3 100644 (file)
@@ -4,7 +4,7 @@ use vars qw($VERSION);
 use Scalar::Util qw(reftype looks_like_number);
 use Carp qw(croak);
 
-$VERSION = "1.011";
+$VERSION = "1.012";
 
 =head1 NAME
 
@@ -103,9 +103,9 @@ sub rotate {
   if ($opts{'x'} || $opts{'y'}) {
     $opts{'x'} ||= 0;
     $opts{'y'} ||= 0;
-    return $class->translate('x'=>-$opts{'x'}, 'y'=>-$opts{'y'})
+    return $class->translate('x'=>$opts{'x'}, 'y'=>$opts{'y'})
       * $class->rotate(radians=>$angle)
-        * $class->translate('x'=>$opts{'x'}, 'y'=>$opts{'y'});
+        * $class->translate('x'=>-$opts{'x'}, 'y'=>-$opts{'y'});
   }
   else {
     my $sin = sin($angle);
@@ -259,6 +259,60 @@ sub matrix {
   }
 }
 
+=item transform($x, $y)
+
+Transform a point the same way matrix_transform does.
+
+=cut
+
+sub transform {
+  my ($self, $x, $y) = @_;
+
+  my $sz = $x * $self->[6] + $y * $self->[7] + $self->[8];
+  my ($sx, $sy);
+  if (abs($sz) > 0.000001) {
+    $sx = ($x * $self->[0] + $y * $self->[1] + $self->[2]) / $sz;
+    $sy = ($x * $self->[3] + $y * $self->[4] + $self->[5]) / $sz;
+  }
+  else {
+    $sx = $sy = 0;
+  }
+
+  return ($sx, $sy);
+}
+
+=item compose(matrix...)
+
+Compose several matrices together for use in transformation.
+
+For example, for three matrices:
+
+  my $out = Imager::Matrix2d->compose($m1, $m2, $m3);
+
+is equivalent to:
+
+  my $out = $m3 * $m2 * $m1;
+
+Returns the identity matrix if no parameters are supplied.
+
+May return the supplied matrix if only one matrix is supplied.
+
+=cut
+
+sub compose {
+  my ($class, @in) = @_;
+
+  @in
+    or return $class->identity;
+
+  my $out = pop @in;
+  for my $m (reverse @in) {
+    $out = $out * $m;
+  }
+
+  return $out;
+}
+
 =item _mult()
 
 Implements the overloaded '*' operator.  Internal use.
@@ -266,6 +320,17 @@ Implements the overloaded '*' operator.  Internal use.
 Currently both the left and right-hand sides of the operator must be
 an Imager::Matrix2d.
 
+When composing a matrix for transformation you should multiply the
+matrices in the reverse order of the transformations:
+
+  my $shear = Imager::Matrix2d->shear(x => 0.1);
+  my $rotate = Imager::Matrix2d->rotate(degrees => 45);
+  my $shear_then_rotate = $rotate * $shear;
+
+or use the compose method:
+
+  my $shear_then_rotate = Imager::Matrix2d->compose($shear, $rotate);
+
 =cut
 
 sub _mult {
index 836d6f8..ce18037 100644 (file)
@@ -1,7 +1,8 @@
 #!perl -w
 use strict;
-use Test::More tests => 23;
+use Test::More tests => 25;
 use Imager;
+use constant EPSILON => 0.000001;
 
 BEGIN { use_ok('Imager::Matrix2d', ':handy') }
 
@@ -105,9 +106,17 @@ is(Imager->errstr, "9 coefficients required", "check error");
     ok($died, "mult by bad scalar died");
     like($@, qr/multiply by array ref or number/, "check message");
   }
-  
 }
 
+{ # rt #99959 Imager::Matrix2d->rotate about (x, y) bug
+  my $rm = Imager::Matrix2d->rotate(degrees => 180, x => 10, y => 5);
+  my ($rx, $ry) = $rm->transform(0, 0);
+  ok(abs($rx - 20) < EPSILON, "x from rotate (0,0) around (10, 5)")
+    or print "# x = $rx\n";
+  ok(abs($ry - 10) < EPSILON, "y from rotate (0,0) around (10, 5)")
+    or print "# y = $ry\n";
+  
+}
 
 sub almost_equal {
   my ($m1, $m2) = @_;