From f480f885a24b8515dec88db3cf20b2a43187ca9c Mon Sep 17 00:00:00 2001 From: Tony Cook Date: Sat, 15 Nov 2014 13:47:48 +1100 Subject: [PATCH] [rt #99959] fix Imager::Matrix2d::rotate()'s centre point hanling also add a compose() method and add notes on the order of multiplcation vs composition of transformations. --- lib/Imager/Matrix2d.pm | 71 +++++++++++++++++++++++++++++++++++++++-- t/900-util/050-matrix.t | 13 ++++++-- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/lib/Imager/Matrix2d.pm b/lib/Imager/Matrix2d.pm index a8ba80d0..3aa2cf39 100644 --- a/lib/Imager/Matrix2d.pm +++ b/lib/Imager/Matrix2d.pm @@ -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 { diff --git a/t/900-util/050-matrix.t b/t/900-util/050-matrix.t index 836d6f8c..ce18037d 100644 --- a/t/900-util/050-matrix.t +++ b/t/900-util/050-matrix.t @@ -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) = @_;