[rt #77063] improve rotate(degrees => ...) results.
authorTony Cook <tony@develop-help.com>
Sat, 26 May 2012 02:32:53 +0000 (12:32 +1000)
committerTony Cook <tony@develop-help.com>
Sat, 26 May 2012 02:32:53 +0000 (12:32 +1000)
Changes
Imager.pm
rotate.im
t/t64copyflip.t

diff --git a/Changes b/Changes
index 3049b95d1ece2abca6ed23f221e2b8796bd74dc3..a9e302b909633376455859fc02cef50bc1d1d7d1 100644 (file)
--- a/Changes
+++ b/Changes
@@ -10,6 +10,16 @@ Imager release history.  Older releases can be found in Changes.old
    load.
    https://rt.cpan.org/Ticket/Display.html?id=77173
 
+ - The size of rotated images is no longer rounded up so aggressively.
+   Added rounding to the linear interpolation done for rotate() and
+   matrix_transform() for 1 and 3 channel 8-bit images.
+   Adjusted the two tranlate matrices used to build the final rotation
+   matrix to use the distance between the outlier pixels rather than
+   the pixel dimensions (ie. dimension-1).
+   With all of this the output of rotate(degrees => 270) on an 8-bit
+   image exactly matches the output of rotate(right => 270).
+   https://rt.cpan.org/Ticket/Display.html?id=77063
+
 Imager 0.90 - 30 Apr 2012
 ===========
 
index 52d8f37249160714bc2fc3c1da80c22a23bdb184..3fbfc7ee8a50bd575a337801885bff53b409e79e 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -2632,7 +2632,7 @@ sub rotate {
     }
   }
   elsif (defined $opts{radians} || defined $opts{degrees}) {
-    my $amount = $opts{radians} || $opts{degrees} * 3.1415926535 / 180;
+    my $amount = $opts{radians} || $opts{degrees} * 3.14159265358979 / 180;
 
     my $back = $opts{back};
     my $result = Imager->new;
index ce8cefc689e166e92c12b3bcd203c25e8db5fdd1..8a2a226bae04864824eafc8493eb22f60344cdaa 100644 (file)
--- a/rotate.im
+++ b/rotate.im
@@ -20,6 +20,8 @@ Other rotations will be added as tuits become available.
 #include "imageri.h"
 #include <math.h> /* for floor() */
 
+#define ROT_DEBUG(x)
+
 i_img *i_rotate90(i_img *src, int degrees) {
   i_img *targ;
   i_img_dim x, y;
@@ -131,7 +133,7 @@ static i_color interp_i_color(i_color before, i_color after, double pos,
   pos -= floor(pos);
   if (channels == 1 || channels == 3) {
     for (ch = 0; ch < channels; ++ch)
-      out.channel[ch] = (1-pos) * before.channel[ch] + pos * after.channel[ch];
+      out.channel[ch] = ((1-pos) * before.channel[ch] + pos * after.channel[ch]) + 0.5;
   }
   else {
     int total_cover = (1-pos) * before.channel[channels-1]
@@ -250,11 +252,13 @@ i_img *i_matrix_transform_bg(i_img *src, i_img_dim xsize, i_img_dim ysize, const
        if (fabs(sz) > 0.0000001 
            && sx >= -1 && sx < src->xsize
            && sy >= -1 && sy < src->ysize) {
-         
+
+         ROT_DEBUG(fprintf(stderr, "map " i_DFp " to %g,%g\n", i_DFcp(x, y), sx, sy));
          if (sx != (i_img_dim)sx) {
            if (sy != (i_img_dim)sy) {
              IM_COLOR c[2][2]; 
              IM_COLOR ci2[2];
+             ROT_DEBUG(fprintf(stderr, " both non-int\n"));
              for (i = 0; i < 2; ++i)
                for (j = 0; j < 2; ++j)
                  if (IM_GPIX(src, floor(sx)+i, floor(sy)+j, &c[j][i]))
@@ -265,6 +269,7 @@ i_img *i_matrix_transform_bg(i_img *src, i_img_dim xsize, i_img_dim ysize, const
            }
            else {
              IM_COLOR ci2[2];
+             ROT_DEBUG(fprintf(stderr, " y int, x non-int\n"));
              for (i = 0; i < 2; ++i)
                if (IM_GPIX(src, floor(sx)+i, sy, ci2+i))
                  ci2[i] = back;
@@ -274,12 +279,14 @@ i_img *i_matrix_transform_bg(i_img *src, i_img_dim xsize, i_img_dim ysize, const
          else {
            if (sy != (i_img_dim)sy) {
              IM_COLOR ci2[2];
+             ROT_DEBUG(fprintf(stderr, " x int, y non-int\n"));
              for (i = 0; i < 2; ++i)
                if (IM_GPIX(src, sx, floor(sy)+i, ci2+i))
                  ci2[i] = back;
              vals[x] = interp_i_color(ci2[0], ci2[1], sy, src->channels);
            }
            else {
+             ROT_DEBUG(fprintf(stderr, " both int\n"));
              /* all the world's an integer */
              if (IM_GPIX(src, sx, sy, vals+x))
                vals[x] = back;
@@ -393,6 +400,15 @@ i_matrix_mult(double *dest, const double *left, const double *right) {
   }
 }
 
+#define numfmt "%23g"
+
+ROT_DEBUG(static dump_mat(const char *name, double *f) {
+  fprintf(stderr, "%s:\n  " numfmt " " numfmt " " numfmt "\n"
+         "  " numfmt " " numfmt " " numfmt "\n"
+         "  " numfmt " " numfmt " " numfmt "\n",
+         name, f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8]);
+  })
+
 i_img *i_rotate_exact_bg(i_img *src, double amount, 
                         const i_color *backp, const i_fcolor *fbackp) {
   double xlate1[9] = { 0 };
@@ -403,11 +419,13 @@ i_img *i_rotate_exact_bg(i_img *src, double amount,
 
   /* first translate the centre of the image to (0,0) */
   xlate1[0] = 1;
-  xlate1[2] = src->xsize/2.0;
+  xlate1[2] = (src->xsize-1)/2.0;
   xlate1[4] = 1;
-  xlate1[5] = src->ysize/2.0;
+  xlate1[5] = (src->ysize-1)/2.0;
   xlate1[8] = 1;
 
+  ROT_DEBUG(dump_mat("xlate1", xlate1));
+
   /* rotate around (0.0) */
   rotate[0] = cos(amount);
   rotate[1] = sin(amount);
@@ -419,21 +437,31 @@ i_img *i_rotate_exact_bg(i_img *src, double amount,
   rotate[7] = 0;
   rotate[8] = 1;
 
-  x1 = ceil(fabs(src->xsize * rotate[0] + src->ysize * rotate[1]));
-  x2 = ceil(fabs(src->xsize * rotate[0] - src->ysize * rotate[1]));
-  y1 = ceil(fabs(src->xsize * rotate[3] + src->ysize * rotate[4]));
-  y2 = ceil(fabs(src->xsize * rotate[3] - src->ysize * rotate[4]));
+  ROT_DEBUG(dump_mat("rotate", rotate));
+
+  ROT_DEBUG(fprintf(stderr, "cos %g sin %g\n", rotate[0], rotate[1]));
+
+  x1 = ceil(fabs(src->xsize * rotate[0] + src->ysize * rotate[1]) - 0.0001);
+  x2 = ceil(fabs(src->xsize * rotate[0] - src->ysize * rotate[1]) - 0.0001);
+  y1 = ceil(fabs(src->xsize * rotate[3] + src->ysize * rotate[4]) - 0.0001);
+  y2 = ceil(fabs(src->xsize * rotate[3] - src->ysize * rotate[4]) - 0.0001);
+  ROT_DEBUG(fprintf(stderr, "x1 y1 " i_DFp " x2 y2 " i_DFp "\n", i_DFcp(x1, y1), i_DFcp(x2, y2)));
   newxsize = x1 > x2 ? x1 : x2;
   newysize = y1 > y2 ? y1 : y2;
   /* translate the centre back to the center of the image */
   xlate2[0] = 1;
-  xlate2[2] = -newxsize/2.0;
+  xlate2[2] = -(newxsize-1)/2.0;
   xlate2[4] = 1;
-  xlate2[5] = -newysize/2.0;
+  xlate2[5] = -(newysize-1)/2.0;
   xlate2[8] = 1;
+
+  ROT_DEBUG(dump_mat("xlate2", xlate2));
+
   i_matrix_mult(temp, xlate1, rotate);
   i_matrix_mult(matrix, temp, xlate2);
 
+  ROT_DEBUG(dump_mat("matrxi", matrix));
+
   return i_matrix_transform_bg(src, newxsize, newysize, matrix, backp, fbackp);
 }
 
index 5a48c0584d84e325b17591f41cfbc558de27bea5..1a0a43686e9f5622f324c6ffe484356f6a054a70 100644 (file)
@@ -1,8 +1,8 @@
 #!perl -w
 use strict;
-use Test::More tests => 83;
+use Test::More tests => 87;
 use Imager;
-use Imager::Test qw(is_color3 is_image is_imaged test_image_double test_image isnt_image);
+use Imager::Test qw(is_color3 is_image is_imaged test_image_double test_image isnt_image is_image_similar);
 
 #$Imager::DEBUG=1;
 
@@ -234,3 +234,23 @@ sub rot_test {
   is_color3($colors[0], 0, 0, 0, "check we got black");
   is_color3($colors[1], 255, 0, 0, "and red");
 }
+
+{ # RT #77063 rotate with degrees => 270 gives a black border
+  # so be a little less strict about rounding up
+  # I've also:
+  #  - improved calculation of the rotation matrix
+  #  - added rounding to interpolation for 1/3 channel images
+  my $im = test_image;
+  $im->box(color => "#00F");
+  my $right = $im->rotate(right => 270);
+  my $deg = $im->rotate(degrees => 270, back => "#FFF");
+  is($deg->getwidth, 150, "check degrees => 270 width");
+  is($deg->getheight, 150, "check degrees => 270 height");
+  ok($deg->write(file => "testout/t64rotdeg270.ppm"), "save it");
+  # deg->write(file => "testout/t64rotright270.ppm");
+  is_image($deg, $right, "check right and degrees result the same");
+  #$deg = $deg->convert(preset => "addalpha");
+  # $right = $right->convert(preset => "addalpha");
+  # my $diff = $right->difference(other => $deg, mindist => 1);
+  # $diff->write(file => "testout/t64rotdiff.png");
+}