]> git.imager.perl.org - imager.git/commitdiff
Merged in the scale branch:
authorTony Cook <tony@develop=help.com>
Fri, 18 Aug 2006 04:35:27 +0000 (04:35 +0000)
committerTony Cook <tony@develop=help.com>
Fri, 18 Aug 2006 04:35:27 +0000 (04:35 +0000)
- adds a new scaling mechanism 'mixing' based on the method
implemented by pnmscale.  Produces better detail when scaling down and
is faster than the 'normal' method.

- the scale() method can now scale non-proportionally if the caller
specifically asks for it with xscalefactor/yscalefactor or by setting
type to 'nonprop'.

Imager.pm
Imager.xs
MANIFEST
Makefile.PL
TODO
imager.h
lib/Imager/Transformations.pod
scale.c [new file with mode: 0644]
t/t40scale.t

index e4ae9bf87b42ab97b43b9ba0678c5a7b3399c429..cf51197d0c35f7787ef02bbdbf78e67c668938ce 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -1963,11 +1963,10 @@ sub register_filter {
 
 sub scale {
   my $self=shift;
 
 sub scale {
   my $self=shift;
-  my %opts=(scalefactor=>0.5,'type'=>'max',qtype=>'normal',@_);
+  my %opts=('type'=>'max',qtype=>'normal',@_);
   my $img = Imager->new();
   my $tmp = Imager->new();
   my $img = Imager->new();
   my $tmp = Imager->new();
-
-  my $scalefactor = $opts{scalefactor};
+  my ($x_scale, $y_scale);
 
   unless (defined wantarray) {
     my @caller = caller;
 
   unless (defined wantarray) {
     my @caller = caller;
@@ -1980,45 +1979,67 @@ sub scale {
     return undef;
   }
 
     return undef;
   }
 
+  if ($opts{'xscalefactor'} && $opts{'yscalefactor'}) {
+    $x_scale = $opts{'xscalefactor'};
+    $y_scale = $opts{'yscalefactor'};
+  }
+  elsif ($opts{'xscalefactor'}) {
+    $x_scale = $opts{'xscalefactor'};
+    $y_scale = $opts{'scalefactor'} || $x_scale;
+  }
+  elsif ($opts{'yscalefactor'}) {
+    $y_scale = $opts{'yscalefactor'};
+    $x_scale = $opts{'scalefactor'} || $y_scale;
+  }
+  else {
+    $x_scale = $y_scale = $opts{'scalefactor'} || 0.5;
+  }
+
   # work out the scaling
   if ($opts{xpixels} and $opts{ypixels} and $opts{'type'}) {
     my ($xpix, $ypix)=( $opts{xpixels} / $self->getwidth() , 
                        $opts{ypixels} / $self->getheight() );
     if ($opts{'type'} eq 'min') { 
   # work out the scaling
   if ($opts{xpixels} and $opts{ypixels} and $opts{'type'}) {
     my ($xpix, $ypix)=( $opts{xpixels} / $self->getwidth() , 
                        $opts{ypixels} / $self->getheight() );
     if ($opts{'type'} eq 'min') { 
-      $scalefactor = _min($xpix,$ypix); 
+      $x_scale = $y_scale = _min($xpix,$ypix); 
     }
     elsif ($opts{'type'} eq 'max') {
     }
     elsif ($opts{'type'} eq 'max') {
-      $scalefactor = _max($xpix,$ypix);
+      $x_scale = $y_scale = _max($xpix,$ypix);
+    }
+    elsif ($opts{'type'} eq 'nonprop' || $opts{'type'} eq 'non-proportional') {
+      $x_scale = $xpix;
+      $y_scale = $ypix;
     }
     else {
       $self->_set_error('invalid value for type parameter');
       return undef;
     }
   } elsif ($opts{xpixels}) { 
     }
     else {
       $self->_set_error('invalid value for type parameter');
       return undef;
     }
   } elsif ($opts{xpixels}) { 
-    $scalefactor = $opts{xpixels} / $self->getwidth();
+    $x_scale = $y_scale = $opts{xpixels} / $self->getwidth();
   }
   elsif ($opts{ypixels}) { 
   }
   elsif ($opts{ypixels}) { 
-    $scalefactor = $opts{ypixels}/$self->getheight();
+    $x_scale = $y_scale = $opts{ypixels}/$self->getheight();
   }
   elsif ($opts{constrain} && ref $opts{constrain}
         && $opts{constrain}->can('constrain')) {
     # we've been passed an Image::Math::Constrain object or something
     # that looks like one
   }
   elsif ($opts{constrain} && ref $opts{constrain}
         && $opts{constrain}->can('constrain')) {
     # we've been passed an Image::Math::Constrain object or something
     # that looks like one
+    my $scalefactor;
     (undef, undef, $scalefactor)
       = $opts{constrain}->constrain($self->getwidth, $self->getheight);
     unless ($scalefactor) {
       $self->_set_error('constrain method failed on constrain parameter');
       return undef;
     }
     (undef, undef, $scalefactor)
       = $opts{constrain}->constrain($self->getwidth, $self->getheight);
     unless ($scalefactor) {
       $self->_set_error('constrain method failed on constrain parameter');
       return undef;
     }
+    $x_scale = $y_scale = $scalefactor;
   }
 
   if ($opts{qtype} eq 'normal') {
   }
 
   if ($opts{qtype} eq 'normal') {
-    $tmp->{IMG} = i_scaleaxis($self->{IMG}, $scalefactor, 0);
+    $tmp->{IMG} = i_scaleaxis($self->{IMG}, $x_scale, 0);
     if ( !defined($tmp->{IMG}) ) { 
       $self->{ERRSTR} = 'unable to scale image';
       return undef;
     }
     if ( !defined($tmp->{IMG}) ) { 
       $self->{ERRSTR} = 'unable to scale image';
       return undef;
     }
-    $img->{IMG}=i_scaleaxis($tmp->{IMG}, $scalefactor, 1);
+    $img->{IMG}=i_scaleaxis($tmp->{IMG}, $y_scale, 1);
     if ( !defined($img->{IMG}) ) { 
       $self->{ERRSTR}='unable to scale image'; 
       return undef;
     if ( !defined($img->{IMG}) ) { 
       $self->{ERRSTR}='unable to scale image'; 
       return undef;
@@ -2027,13 +2048,25 @@ sub scale {
     return $img;
   }
   elsif ($opts{'qtype'} eq 'preview') {
     return $img;
   }
   elsif ($opts{'qtype'} eq 'preview') {
-    $img->{IMG} = i_scale_nn($self->{IMG}, $scalefactor, $scalefactor); 
+    $img->{IMG} = i_scale_nn($self->{IMG}, $x_scale, $y_scale); 
     if ( !defined($img->{IMG}) ) { 
       $self->{ERRSTR}='unable to scale image'; 
       return undef;
     }
     return $img;
   }
     if ( !defined($img->{IMG}) ) { 
       $self->{ERRSTR}='unable to scale image'; 
       return undef;
     }
     return $img;
   }
+  elsif ($opts{'qtype'} eq 'mixing') {
+    my $new_width = int(0.5 + $self->getwidth * $x_scale);
+    my $new_height = int(0.5 + $self->getheight * $y_scale);
+    $new_width >= 1 or $new_width = 1;
+    $new_height >= 1 or $new_height = 1;
+    $img->{IMG} = i_scale_mixing($self->{IMG}, $new_width, $new_height);
+    unless ($img->{IMG}) {
+      $self->_set_error(Imager->_error_as_meg);
+      return;
+    }
+    return $img;
+  }
   else {
     $self->_set_error('invalid value for qtype parameter');
     return undef;
   else {
     $self->_set_error('invalid value for qtype parameter');
     return undef;
index d929c20811798801fcec69a9d5b8ce4d85402291..82ae7efe37bc2b45d7fff1471f54b121ed556c61 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -2973,6 +2973,12 @@ i_scale_nn(im,scx,scy)
              float     scx
              float     scy
 
              float     scx
              float     scy
 
+Imager::ImgRaw
+i_scale_mixing(im, width, height)
+    Imager::ImgRaw     im
+              int     width
+              int     height
+
 Imager::ImgRaw
 i_haar(im)
     Imager::ImgRaw     im
 Imager::ImgRaw
 i_haar(im)
     Imager::ImgRaw     im
index 0d0326af527133a7984687cebedd9fe6f0ee612c..dd422f059d6b049d0a90d7b25a435f91c7150bc9 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -189,6 +189,7 @@ samples/samp-tags.cgi   Demonstrate image upload via a HTML form
 samples/samp-tags.html  Form for samp-tags.cgi
 samples/slant_text.pl   Using $font->transform() to slant text
 samples/tk-photo.pl
 samples/samp-tags.html  Form for samp-tags.cgi
 samples/slant_text.pl   Using $font->transform() to slant text
 samples/tk-photo.pl
+scale.c                Newer scaling code
 spot.perl      For making an ordered dither matrix from a spot function
 stackmach.c
 stackmach.h
 spot.perl      For making an ordered dither matrix from a spot function
 stackmach.c
 stackmach.h
index 577b0c5940a768b43a630a6ae37a3e6866b80676..a7e6ee6c2a6b92633f0928278c6d49ae299578be 100644 (file)
@@ -155,7 +155,7 @@ my @objs = qw(Imager.o draw.o polygon.o image.o io.o iolayer.o
               regmach.o trans2.o quant.o error.o convert.o
               map.o tags.o palimg.o maskimg.o img16.o rotate.o
               bmp.o tga.o rgb.o color.o fills.o imgdouble.o limits.o hlines.o
               regmach.o trans2.o quant.o error.o convert.o
               map.o tags.o palimg.o maskimg.o img16.o rotate.o
               bmp.o tga.o rgb.o color.o fills.o imgdouble.o limits.o hlines.o
-              imext.o);
+              imext.o scale.o);
 
 $Recommends{Imager} =
   { 'Parse::RecDescent' => 0 };
 
 $Recommends{Imager} =
   { 'Parse::RecDescent' => 0 };
diff --git a/TODO b/TODO
index 7e5587fb38f69f5f6d2e44e1ab7b51212a75e483..ec27cad64e6837bf14dadcd666d2f0400cc7aa6b 100644 (file)
--- a/TODO
+++ b/TODO
@@ -14,10 +14,12 @@ remove gif query from makefile.pl (done)
 
 fallback for read/write_multi to read/write
 
 
 fallback for read/write_multi to read/write
 
-pnmscale based alternative scale method
+pnmscale based alternative scale method (done)
 
 rubthrough 4 on 4
 
 
 rubthrough 4 on 4
 
+replace dummy test script in dynfilt with a real one
+
 BEFORE 0.50:
 
 skip t82inline.t tests if directory has spaces in name
 BEFORE 0.50:
 
 skip t82inline.t tests if directory has spaces in name
index 64c0c293eee9e4785221c489f022cefc2771f3d4..e59978058aee5a86a8b6ba013e550d0c00ede122 100644 (file)
--- a/imager.h
+++ b/imager.h
@@ -417,6 +417,7 @@ undef_int i_writergb_wiol(i_img *img, io_glue *ig, int wierdpack, int compress,
 
 i_img * i_scaleaxis(i_img *im, float Value, int Axis);
 i_img * i_scale_nn(i_img *im, float scx, float scy);
 
 i_img * i_scaleaxis(i_img *im, float Value, int Axis);
 i_img * i_scale_nn(i_img *im, float scx, float scy);
+i_img * i_scale_mixing(i_img *src, int width, int height);
 i_img * i_haar(i_img *im);
 int     i_count_colors(i_img *im,int maxc);
 
 i_img * i_haar(i_img *im);
 int     i_count_colors(i_img *im,int maxc);
 
index 392698d5bf678013cd23d9a6d51019a35e717e4c..23adf0ba7fe4ada2354b78fc76b3f5983b86f6c3 100644 (file)
@@ -94,6 +94,8 @@ wide and 500 pixels tall.
   $newimg = $img->scale(xpixels=>400,ypixels=>400); # 560x400
   $newimg = $img->scale(xpixels=>400,ypixels=>400,type=>'min'); # 400x285
 
   $newimg = $img->scale(xpixels=>400,ypixels=>400); # 560x400
   $newimg = $img->scale(xpixels=>400,ypixels=>400,type=>'min'); # 400x285
 
+  $newimg = $img->scale(xpixels=>400, ypixels=>400),type=>'nonprop'); # 400x400
+
   $newimg = $img->scale(scalefactor=>0.25); 175x125 
   $newimg = $img->scale(); # 350x250
 
   $newimg = $img->scale(scalefactor=>0.25); 175x125 
   $newimg = $img->scale(); # 350x250
 
@@ -108,9 +110,9 @@ the original.
 
 =item *
 
 
 =item *
 
-xpixels, ypixels - desired size of the scaled image.  The resulting
-image is always scaled proportionally.  The C<type> parameter controls
-whether the larger or smaller of the two possible sizes is chosen.
+xpixels, ypixels - desired size of the scaled image.  The C<type>
+parameter controls whether the larger or smaller of the two possible
+sizes is chosen, or if the image is scaled non-proportionally.
 
 =item *
 
 
 =item *
 
@@ -119,8 +121,15 @@ the image size should be constrained.
 
 =item *
 
 
 =item *
 
-scalefactor - if none of xpixels, ypixels or constrain is supplied
-then this is used as the ratio to scale by.  Default: 0.5.
+scalefactor - if none of xpixels, ypixels, xscalefactor, yscalefactor
+or constrain is supplied then this is used as the ratio to scale by.
+Default: 0.5.
+
+=item *
+
+xscalefactor, yscalefactor - if both are supplied then the image is
+scaled as per these parameters, whether this is proportionally or not.
+New in Imager 0.54.
 
 =item *
 
 
 =item *
 
@@ -137,6 +146,10 @@ min - the smaller of the 2 sizes are chosen.
 
 max - the larger of the 2 sizes.  This is the default.
 
 
 max - the larger of the 2 sizes.  This is the default.
 
+=item *
+
+nonprop - non-proportional scaling.  New in Imager 0.54.
+
 =back
 
 scale() will fail if C<type> is set to some other value.
 =back
 
 scale() will fail if C<type> is set to some other value.
@@ -156,11 +169,22 @@ qtype - defines the quality of scaling performed.  Possible values are:
 
 =item *
 
 
 =item *
 
-normal - high quality scaling.  This is the default.
+C<normal> - high quality scaling.  This is the default.
 
 =item *
 
 
 =item *
 
-preview - lower quality.
+C<preview> - lower quality.  When scaling down this will skip input
+pixels, eg. scaling by 0.5 will skip every other pixel.  When scaling
+up this will duplicate pixels.
+
+=item *
+
+C<mixing> - implements the mixing algorithm implemented by pnmscale.
+This retains more detail when scaling down than C<normal>.  When
+scaling down this proportionally accumulates sample data from the
+pixels, resulting in a proportional mix of all of the pixels.  When
+scaling up this will mix pixels when the sampling grid crosses a pixel
+boundary but will otherwise copy pixel values.
 
 =back
 
 
 =back
 
@@ -174,6 +198,16 @@ dimensions. eg.
 
   my $scaled = $img->scaleX(pixels=>400)->scaleY(pixels=>200);
 
 
   my $scaled = $img->scaleX(pixels=>400)->scaleY(pixels=>200);
 
+From Imager 0.54 you can scale without maintaining proportions either
+by supplying both the xscalefactor and yscalefactor arguments:
+
+  my $scaled = $img->scale(xscalefactor => 0.5, yscalefactor => 0.67);
+
+or by supplying C<xpixels> and C<ypixels> and setting C<type> to
+"nonprop":
+
+  my $scaled = $im->scale(xpixels => 200, ypixels => 200, type => 'nonprop');
+
 Returns the scaled image on success.
 
 Returns false on failure, check the errstr() method for the reason for
 Returns the scaled image on success.
 
 Returns false on failure, check the errstr() method for the reason for
@@ -210,6 +244,9 @@ A mandatory warning is produced if scale() is called in void context.
   # to half size
   my $low = $image->scale(qtype => 'preview');
 
   # to half size
   my $low = $image->scale(qtype => 'preview');
 
+  # mixing method scale
+  my $mixed = $image->scale(qtype => 'mixing', scalefactor => 0.1);
+
   # using an Image::Math::Constrain object
   use Image::Math::Constrain;
   my $constrain = Image::Math::Constrain->new(800, 600);
   # using an Image::Math::Constrain object
   use Image::Math::Constrain;
   my $constrain = Image::Math::Constrain->new(800, 600);
diff --git a/scale.c b/scale.c
new file mode 100644 (file)
index 0000000..2c51c91
--- /dev/null
+++ b/scale.c
@@ -0,0 +1,221 @@
+#include "imager.h"
+
+/*
+ * i_scale_mixing() is based on code contained in pnmscale.c, part of
+ * the netpbm distribution.  No code was copied from pnmscale but
+ * the algorthm was and for this I thank the netpbm crew.
+ *
+ * Tony
+ */
+
+/* pnmscale.c - read a portable anymap and scale it
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+*/
+
+
+static void
+zero_row(i_fcolor *row, int width, int channels);
+static void
+accum_output_row(i_fcolor *accum, double fraction, i_fcolor const *in,
+                int width, int channels);
+static void
+horizontal_scale(i_fcolor *out, int out_width, 
+                i_fcolor const *in, int in_width,
+                int channels);
+
+/*
+=item i_scale_mixing
+
+Returns a new image scaled to the given size.
+
+Unlike i_scale_axis() this does a simple coverage of pixels from
+source to target and doesn't resample.
+
+Adapted from pnmscale.
+
+=cut
+*/
+i_img *
+i_scale_mixing(i_img *src, int x_out, int y_out) {
+  i_img *result;
+  i_fcolor *in_row = NULL;
+  i_fcolor *xscale_row = NULL;
+  i_fcolor *accum_row = NULL;
+  int y;
+  int in_row_bytes, out_row_bytes;
+  double rowsleft, fracrowtofill;
+  int rowsread;
+  double y_scale;
+
+  mm_log((1, "i_scale_mixing(src %p, x_out %d, y_out %d)\n", 
+         src, x_out, y_out));
+
+  i_clear_error();
+
+  if (x_out <= 0) {
+    i_push_errorf(0, "output width %d invalid", x_out);
+    return NULL;
+  }
+  if (y_out <= 0) {
+    i_push_errorf(0, "output height %d invalid", y_out);
+    return NULL;
+  }
+
+  in_row_bytes = sizeof(i_fcolor) * src->xsize;
+  if (in_row_bytes / sizeof(i_fcolor) != src->xsize) {
+    i_push_error(0, "integer overflow allocating input row buffer");
+    return NULL;
+  }
+  out_row_bytes = sizeof(i_fcolor) * x_out;
+  if (out_row_bytes / sizeof(i_fcolor) != x_out) {
+    i_push_error(0, "integer overflow allocating output row buffer");
+    return NULL;
+  }
+
+  if (x_out == src->xsize && y_out == src->ysize) {
+    return i_copy(src);
+  }
+
+  y_scale = y_out / (double)src->ysize;
+
+  result = i_sametype_chans(src, x_out, y_out, src->channels);
+  if (!result)
+    return NULL;
+
+  in_row     = mymalloc(in_row_bytes);
+  accum_row  = mymalloc(in_row_bytes);
+  xscale_row = mymalloc(out_row_bytes);
+
+  rowsread = 0;
+  rowsleft = 0.0;
+  for (y = 0; y < y_out; ++y) {
+    if (y_out == src->ysize) {
+      i_glinf(src, 0, src->xsize, y, accum_row);
+    }
+    else {
+      fracrowtofill = 1.0;
+      zero_row(accum_row, src->xsize, src->channels);
+      while (fracrowtofill > 0) {
+       if (rowsleft <= 0) {
+         if (rowsread < src->ysize) {
+           i_glinf(src, 0, src->xsize, rowsread, in_row);
+           ++rowsread;
+         }
+         /* else just use the last row read */
+
+         rowsleft = y_scale;
+       }
+       if (rowsleft < fracrowtofill) {
+         accum_output_row(accum_row, rowsleft, in_row, src->xsize, 
+                          src->channels);
+         fracrowtofill -= rowsleft;
+         rowsleft = 0;
+       }
+       else {
+         accum_output_row(accum_row, fracrowtofill, in_row, src->xsize, 
+                          src->channels);
+         rowsleft -= fracrowtofill;
+         fracrowtofill = 0;
+       }
+      }
+      /* we've accumulated a vertically scaled row */
+      if (x_out == src->xsize) {
+       /* no need to scale */
+       i_plinf(result, 0, x_out, y, accum_row);
+      }
+      else {
+       horizontal_scale(xscale_row, x_out, accum_row, src->xsize, 
+                        src->channels);
+       i_plinf(result, 0, x_out, y, xscale_row);
+      }
+    }
+  }
+
+  myfree(in_row);
+  myfree(accum_row);
+  myfree(xscale_row);
+
+  return result;
+}
+
+static void
+zero_row(i_fcolor *row, int width, int channels) {
+  int x;
+  int ch;
+
+  /* with IEEE floats we could just use memset() but that's not
+     safe in general under ANSI C */
+  for (x = 0; x < width; ++x) {
+    for (ch = 0; ch < channels; ++ch)
+      row[x].channel[ch] = 0.0;
+  }
+}
+
+static void
+accum_output_row(i_fcolor *accum, double fraction, i_fcolor const *in,
+                int width, int channels) {
+  int x, ch;
+
+  for (x = 0; x < width; ++x) {
+    for (ch = 0; ch < channels; ++ch) {
+      accum[x].channel[ch] += in[x].channel[ch] * fraction;
+    }
+  }
+}
+
+static void
+horizontal_scale(i_fcolor *out, int out_width, 
+                i_fcolor const *in, int in_width,
+                int channels) {
+  double frac_col_to_fill, frac_col_left;
+  int in_x;
+  int out_x;
+  double x_scale = (double)out_width / in_width;
+  int ch;
+  double accum[MAXCHANNELS] = { 0 };
+  
+  frac_col_to_fill = 1.0;
+  out_x = 0;
+  for (in_x = 0; in_x < in_width; ++in_x) {
+    frac_col_left = x_scale;
+    while (frac_col_left >= frac_col_to_fill) {
+      for (ch = 0; ch < channels; ++ch)
+       accum[ch] += frac_col_to_fill * in[in_x].channel[ch];
+
+      for (ch = 0; ch < channels; ++ch) {
+       out[out_x].channel[ch] = accum[ch];
+       accum[ch] = 0;
+      }
+      frac_col_left -= frac_col_to_fill;
+      frac_col_to_fill = 1.0;
+      ++out_x;
+    }
+
+    if (frac_col_left > 0) {
+      for (ch = 0; ch < channels; ++ch) {
+       accum[ch] += frac_col_left * in[in_x].channel[ch];
+      }
+      frac_col_to_fill -= frac_col_left;
+    }
+  }
+
+  if (out_x < out_width-1 || out_x > out_width) {
+    i_fatal(3, "Internal error: out_x %d out of range (width %d)", out_x, out_width);
+  }
+  
+  if (out_x < out_width) {
+    for (ch = 0; ch < channels; ++ch) {
+      accum[ch] += frac_col_to_fill * in[in_width-1].channel[ch];
+      out[out_x].channel[ch] = accum[ch];
+    }
+  }
+}
index 38ac9a60190c74a8bb3195832dba972adfa80375..cd3441111881fce7382e1acf51aaa853e94e25eb 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 use strict;
 use lib 't';
 #!perl -w
 use strict;
 use lib 't';
-use Test::More tests => 68;
+use Test::More tests => 213;
 
 BEGIN { use_ok(Imager=>':all') }
 
 
 BEGIN { use_ok(Imager=>':all') }
 
@@ -26,6 +26,11 @@ ok($scaleimg, "scale it (preview)") or print "# ",$img->errstr,"\n";
 ok($scaleimg->write(file=>'testout/t40scale2.ppm',type=>'pnm'),
    "write preview scaled image")  or print "# ",$img->errstr,"\n";
 
 ok($scaleimg->write(file=>'testout/t40scale2.ppm',type=>'pnm'),
    "write preview scaled image")  or print "# ",$img->errstr,"\n";
 
+$scaleimg = $img->scale(scalefactor => 0.25, qtype => 'mixing');
+ok($scaleimg, "scale it (mixing)") or print "# ", $img->errstr, "\n";
+ok($scaleimg->write(file=>'testout/t40scale3.ppm', type=>'pnm'),
+   "write mixing scaled image") or print "# ", $img->errstr, "\n";
+
 {
   # check for a warning when scale() is called in void context
   my $warning;
 {
   # check for a warning when scale() is called in void context
   my $warning;
@@ -60,6 +65,10 @@ ok($scaleimg->write(file=>'testout/t40scale2.ppm',type=>'pnm'),
   $out = $img->scale(scalefactor=>0.00001, qtype => 'preview');
   is($out->getwidth, 1, "min scale width (preview)");
   is($out->getheight, 1, "min scale height (preview)");
   $out = $img->scale(scalefactor=>0.00001, qtype => 'preview');
   is($out->getwidth, 1, "min scale width (preview)");
   is($out->getheight, 1, "min scale height (preview)");
+
+  $out = $img->scale(scalefactor=>0.00001, qtype => 'mixing');
+  is($out->getwidth, 1, "min scale width (mixing)");
+  is($out->getheight, 1, "min scale height (mixing)");
 }
 
 { # error handling - NULL image
 }
 
 { # error handling - NULL image
@@ -113,6 +122,22 @@ SKIP:
   scale_test($im, 'scale', 120, 72, "72 height",
             ypixels => 72);
 
   scale_test($im, 'scale', 120, 72, "72 height",
             ypixels => 72);
 
+  # new scaling parameters in 0.54
+  scale_test($im, 'scale', 80, 48, "xscale 0.5",
+            xscalefactor => 0.5);
+  scale_test($im, 'scale', 80, 48, "yscale 0.5",
+            yscalefactor => 0.5);
+  scale_test($im, 'scale', 40, 48, "xscale 0.25 yscale 0.5",
+            xscalefactor => 0.25, yscalefactor => 0.5);
+  scale_test($im, 'scale', 160, 48, "xscale 1.0 yscale 0.5",
+            xscalefactor => 1.0, yscalefactor => 0.5);
+  scale_test($im, 'scale', 160, 48, "xpixels 160 ypixels 48 type nonprop",
+            xpixels => 160, ypixels => 48, type => 'nonprop');
+  scale_test($im, 'scale', 160, 96, "xpixels 160 ypixels 96",
+            xpixels => 160, ypixels => 96);
+  scale_test($im, 'scale', 80, 96, "xpixels 80 ypixels 96 type nonprop",
+            xpixels => 80, ypixels => 96, type => 'nonprop');
+
   # scaleX
   scale_test($im, 'scaleX', 80, 96, "defaults");
   scale_test($im, 'scaleX', 40, 96, "0.25 scalefactor",
   # scaleX
   scale_test($im, 'scaleX', 80, 96, "defaults");
   scale_test($im, 'scaleX', 40, 96, "0.25 scalefactor",
@@ -132,12 +157,14 @@ sub scale_test {
   my ($in, $method, $exp_width, $exp_height, $note, @parms) = @_;
 
   print "# $note: @parms\n";
   my ($in, $method, $exp_width, $exp_height, $note, @parms) = @_;
 
   print "# $note: @parms\n";
- SKIP:
-  {
-    my $scaled = $in->$method(@parms);
-    ok($scaled, "$method $note")
-      or skip("failed to scale", 2);
-    is($scaled->getwidth, $exp_width, "check width");
-    is($scaled->getheight, $exp_height, "check height");
+  for my $qtype (qw(normal preview mixing)) {
+  SKIP:
+    {
+      my $scaled = $in->$method(@parms, qtype => $qtype);
+      ok($scaled, "$method $note qtype $qtype")
+       or skip("failed to scale", 2);
+      is($scaled->getwidth, $exp_width, "check width");
+      is($scaled->getheight, $exp_height, "check height");
+    }
   }
 }
   }
 }