author Tony Cook Sat, 15 Nov 2014 02:47:48 +0000 (13:47 +1100) committer Tony Cook 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 patch | blob | blame | history t/900-util/050-matrix.t patch | blob | blame | history

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";

@@ -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->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-> + \$y * \$self-> + \$self->;
+  my (\$sx, \$sy);
+  if (abs(\$sz) > 0.000001) {
+    \$sx = (\$x * \$self-> + \$y * \$self-> + \$self->) / \$sz;
+    \$sy = (\$x * \$self-> + \$y * \$self-> + \$self->) / \$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) = @_;