added cleanup, documentation, further tests and grayscale support
authorTony Cook <tony@develop=help.com>
Sat, 25 Aug 2007 10:36:11 +0000 (10:36 +0000)
committerTony Cook <tony@develop=help.com>
Sat, 25 Aug 2007 10:36:11 +0000 (10:36 +0000)
Changes
Imager.pm
Imager.xs
image.c
imager.h
imdatatypes.h
lib/Imager/ImageTypes.pod
t/t90cc.t
t/t93podcover.t

diff --git a/Changes b/Changes
index d85ecb3..4ebb29e 100644 (file)
--- a/Changes
+++ b/Changes
@@ -10,6 +10,11 @@ Imager 0.60
 
  - logging functions are now available in the API
 
+ - applied Gabriel Vasseur's patch
+   added documentation, further tests, and support for greyscale images
+   Obviously problems are my fault :)
+   https://rt.cpan.org/Ticket/Display.html?id=28142
+
 Bug fixes:
 
  - in some cases it's possible for giflib/libungif to return color 
index 065b29b..a5c039c 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -3272,28 +3272,56 @@ sub getcolorcount {
 # Returns a reference to a hash. The keys are colour named (packed) and the
 # values are the number of pixels in this colour.
 sub getcolorusagehash {
-    my $self = shift;
-    my $channels= $self->getchannels;
-    # We don't want to look at the alpha channel, because some gifs using it
-    # doesn't define it for every colour (but only for some)
-    $channels -= 1 if $channels == 2 or $channels == 4;
-    my %colour_use;
-    my $height = $self->getheight;
-    for my $y (0 .. $height - 1) {
-        my $colours = $self->getsamples(y => $y, channels => [ 0 .. $channels - 1 ]);
-        while (length $colours) {
-            $colour_use{ substr($colours, 0, $channels, '') }++;
-        }
+  my $self = shift;
+  
+  my %opts = ( maxcolors => 2**30, @_ );
+  my $max_colors = $opts{maxcolors};
+  unless (defined $max_colors && $max_colors > 0) {
+    $self->_set_error('maxcolors must be a positive integer');
+    return;
+  }
+
+  unless (defined $self->{IMG}) {
+    $self->_set_error('empty input image'); 
+    return;
+  }
+
+  my $channels= $self->getchannels;
+  # We don't want to look at the alpha channel, because some gifs using it
+  # doesn't define it for every colour (but only for some)
+  $channels -= 1 if $channels == 2 or $channels == 4;
+  my %color_use;
+  my $height = $self->getheight;
+  for my $y (0 .. $height - 1) {
+    my $colors = $self->getsamples('y' => $y, channels => [ 0 .. $channels - 1 ]);
+    while (length $colors) {
+      $color_use{ substr($colors, 0, $channels, '') }++;
     }
-    return \%colour_use;
+    keys %color_use > $max_colors
+      and return;
+  }
+  return \%color_use;
 }
 
 # This will return a ordered array of the colour usage. Kind of the sorted
 # version of the values of the hash returned by getcolorusagehash.
 # You might want to add safety checks and change the names, etc...
 sub getcolorusage {
-  my $self=shift;
-  return get_anonymous_colour_usage ($self);
+  my $self = shift;
+
+  my %opts = ( maxcolors => 2**30, @_ );
+  my $max_colors = $opts{maxcolors};
+  unless (defined $max_colors && $max_colors > 0) {
+    $self->_set_error('maxcolors must be a positive integer');
+    return;
+  }
+
+  unless (defined $self->{IMG}) {
+    $self->_set_error('empty input image'); 
+    return undef;
+  }
+
+  return i_get_anonymous_color_histo($self->{IMG}, $max_colors);
 }
 
 # draw string to an image
@@ -3760,6 +3788,10 @@ getcolorcount() -  L<Imager::ImageTypes/getcolorcount>
 getcolors() - L<Imager::ImageTypes/getcolors> - get colors from the image
 palette, if it has one
 
+getcolorusage() - L<Imager::ImageTypes/getcolorusage>
+
+getcolorusagehash() - L<Imager::ImageTypes/getcolorusagehash>
+
 get_file_limits() - L<Imager::Files/"Limiting the sizes of images you read">
 
 getheight() - L<Imager::ImageTypes/getwidth>
index 0ccba04..c59815d 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -2973,18 +2973,17 @@ i_count_colors(im,maxc)
                int     maxc
 
 void
-get_anonymous_colour_usage(Imager::ImgRaw im)
+i_get_anonymous_color_histo(im, maxc = 0x40000000)
+   Imager::ImgRaw  im
+   int maxc
     PPCODE:
         int i;
-        unsigned int ** col_usage = (unsigned int **) mymalloc(sizeof(unsigned int *));
-        unsigned int * p;
-        int col_cnt = get_anonymous_color_histo(im, col_usage);
+        unsigned int * col_usage = NULL;
+        int col_cnt = i_get_anonymous_color_histo(im, &col_usage, maxc);
         EXTEND(SP, col_cnt);
-        p = *col_usage;
-        for (i = 0; i < col_cnt; )  {
-            PUSHs(sv_2mortal(newSViv( p[i++])));
+        for (i = 0; i < col_cnt; i++)  {
+            PUSHs(sv_2mortal(newSViv( col_usage[i])));
         }
-        myfree(p);
         myfree(col_usage);
         XSRETURN(col_cnt);
 
diff --git a/image.c b/image.c
index dfef965..846126c 100644 (file)
--- a/image.c
+++ b/image.c
@@ -1264,9 +1264,10 @@ i_count_colors(i_img *im,int maxc) {
   int channels[3];
   int *samp_chans;
   i_sample_t * samp;
-
   int xsize = im->xsize; 
   int ysize = im->ysize;
+  int samp_cnt = 3 * xsize;
+
   if (im->channels >= 3) {
     samp_chans = NULL;
   }
@@ -1274,7 +1275,7 @@ i_count_colors(i_img *im,int maxc) {
     channels[0] = channels[1] = channels[2] = 0;
     samp_chans = channels;
   }
-  int samp_cnt = 3 * xsize;
+
   ct = octt_new();
 
   samp = (i_sample_t *) mymalloc( xsize * 3 * sizeof(i_sample_t));
@@ -1300,7 +1301,8 @@ i_count_colors(i_img *im,int maxc) {
  * (adapted from the Numerical Recipes)
  */
 /* Needed by get_anonymous_color_histo */
-void hpsort(unsigned int n, int *ra) {
+static void
+hpsort(unsigned int n, unsigned *ra) {
     unsigned int i,
                  ir,
                  j,
@@ -1344,44 +1346,51 @@ void hpsort(unsigned int n, int *ra) {
  * the maxc ;-) and you might want to change the name... */
 /* Uses octt_histo */
 int
-get_anonymous_color_histo(i_img *im, unsigned int **col_usage) {
-    struct octt *ct;
-    int x,y,i;
-    int colorcnt;
-    int maxc = 10000000;
-    unsigned int *col_usage_it;
-    i_sample_t * samp;
-
-    int xsize = im->xsize; 
-    int ysize = im->ysize;
-    int samp_cnt = 3 * xsize;
-    ct = octt_new();
-
-    samp = (i_sample_t *) mymalloc( xsize * 3 * sizeof(i_sample_t));
-
-    colorcnt = 0;
-    for(y = 0; y < ysize; ) {
-        i_gsamp(im, 0, xsize, y++, samp, NULL, 3);
-        for(x = 0; x < samp_cnt; ) {
-            colorcnt += octt_add(ct, samp[x], samp[x+1], samp[x+2]);
-            x += 3;
-            if (colorcnt > maxc) { 
-                octt_delete(ct); 
-                return -1; 
-            }
-        }
+i_get_anonymous_color_histo(i_img *im, unsigned int **col_usage, int maxc) {
+  struct octt *ct;
+  int x,y;
+  int colorcnt;
+  unsigned int *col_usage_it;
+  i_sample_t * samp;
+  int channels[3];
+  int *samp_chans;
+  
+  int xsize = im->xsize; 
+  int ysize = im->ysize;
+  int samp_cnt = 3 * xsize;
+  ct = octt_new();
+  
+  samp = (i_sample_t *) mymalloc( xsize * 3 * sizeof(i_sample_t));
+  
+  if (im->channels >= 3) {
+    samp_chans = NULL;
+  }
+  else {
+    channels[0] = channels[1] = channels[2] = 0;
+    samp_chans = channels;
+  }
+
+  colorcnt = 0;
+  for(y = 0; y < ysize; ) {
+    i_gsamp(im, 0, xsize, y++, samp, samp_chans, 3);
+    for(x = 0; x < samp_cnt; ) {
+      colorcnt += octt_add(ct, samp[x], samp[x+1], samp[x+2]);
+      x += 3;
+      if (colorcnt > maxc) { 
+       octt_delete(ct); 
+       return -1; 
+      }
     }
-    myfree(samp);
-    /* Now that we know the number of colours... */
-    col_usage_it = *col_usage = (unsigned int *) mymalloc(colorcnt * 2 * sizeof(unsigned int));
-    octt_histo(ct, &col_usage_it);
-    hpsort(colorcnt, *col_usage);
-    octt_delete(ct);
-    return colorcnt;
+  }
+  myfree(samp);
+  /* Now that we know the number of colours... */
+  col_usage_it = *col_usage = (unsigned int *) mymalloc(colorcnt * sizeof(unsigned int));
+  octt_histo(ct, &col_usage_it);
+  hpsort(colorcnt, *col_usage);
+  octt_delete(ct);
+  return colorcnt;
 }
 
-
-
 /*
 =back
 
index 6595267..1c02754 100644 (file)
--- a/imager.h
+++ b/imager.h
@@ -425,6 +425,7 @@ 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);
+int i_get_anonymous_color_histo(i_img *im, unsigned int **col_usage, int maxc);
 
 i_img * i_transform(i_img *im, int *opx,int opxl,int *opy,int opyl,double parm[],int parmlen);
 
index e9469e1..2c00b79 100644 (file)
@@ -229,6 +229,7 @@ int octt_add(struct octt *ct,unsigned char r,unsigned char g,unsigned char b);
 void octt_dump(struct octt *ct);
 void octt_count(struct octt *ct,int *tot,int max,int *overflow);
 void octt_delete(struct octt *ct);
+void octt_histo(struct octt *ct, unsigned int **col_usage_it_adr);
 
 /* font bounding box results */
 enum bounding_box_index_t {
index 9299111..81d8cab 100644 (file)
@@ -269,7 +269,9 @@ This takes exactly the same parameters as the new() method.
 
 =back
 
-=head2 Getting Information About an Imager Object
+=head2 Image Attribute functions
+
+These return basic attributes of an image object.
 
 =over
 
@@ -296,20 +298,6 @@ Same details apply as for L<getwidth>.
 To get the number of channels in an image C<getchannels()> is used.
 
 
-=item getcolorcount
-
-It is possible to have Imager find the number of colors in an image by
-with the C<getcolorcount()> method. It requires memory proportionally
-to the number of colors in the image so it is possible to have it stop
-sooner if you only need to know if there are more than a certain
-number of colors in the image.  If there are more colors than asked
-for the function return undef.  Examples:
-
-  if (defined($img->getcolorcount(maxcolors=>512)) {
-    print "Less than 512 colors in image\n";
-  }
-
-
 =item bits
 
 The bits() method retrieves the number of bits used to represent each
@@ -475,6 +463,76 @@ Returns the maximum size of the image's palette.
 
 =back
 
+=head2 Color Distribution
+
+=over
+
+=item getcolorcount
+
+Calculates the number of colors in an image.
+
+The amount of memory used by this is proportional to the number of
+colors present in the image, so to avoid using too much memory you can
+supply a maxcolors parameter to limit the memory used.
+
+Note: getcolorcount() treats the image as an 8-bit per sample image.
+
+=over
+
+=item *
+
+X<maxcolors!getcolorcount>maxcolors - the maximum number of colors to
+return.  Default: unlimited.
+
+=back
+
+  if (defined($img->getcolorcount(maxcolors=>512)) {
+    print "Less than 512 colors in image\n";
+  }
+
+=item getcolorusagehash
+
+Calculates a histogram of colors used by the image.
+
+=over
+
+=item *
+
+X<maxcolors!getcolorusagehash>maxcolors - the maximum number of colors
+to return.  Default: unlimited.
+
+=back
+
+Returns a reference to a hash where the keys are the raw color as
+bytes, and the values are the counts for that color.
+
+The alpha channel of the image is ignored.  If the image is grayscale
+then the hash keys will each be a single character.
+
+  my $colors = $img->getcolorusagehash;
+  my $blue_count = $colors->{pack("CCC", 0, 0, 255)} || 0;
+  print "#0000FF used $blue_count times\n";
+
+=item getcolorusage
+
+Calculates color usage counts and returns just the counts.
+
+=over
+
+=item *
+
+X<maxcolors!getcolorusage>maxcolors - the maximum number of colors to
+return.  Default: unlimited.
+
+=back
+
+Returns a list of the color frequencies in ascending order.
+
+  my @counts = $img->getcolorusage;
+  print "The most common color is used $counts[0] times\n";
+
+=back
+
 =head2 Conversion Between Image Types
 
 Warning: if you draw on a paletted image with colors that aren't in
index 3aeef9a..253db3f 100644 (file)
--- a/t/t90cc.t
+++ b/t/t90cc.t
@@ -1,16 +1,69 @@
 #!perl -w
 use strict;
-use Test::More tests => 4;
+use Test::More tests => 16;
 
 use Imager;
 
 Imager::init('log'=>'testout/t90cc.log');
 
-my $img=Imager->new();
-ok($img->open(file=>'testimg/scale.ppm'), 'load test image')
-  or print "failed: ",$img->{ERRSTR},"\n";
+{
+  my $img=Imager->new();
+  ok($img->open(file=>'testimg/scale.ppm'), 'load test image')
+    or print "failed: ",$img->{ERRSTR},"\n";
+  
+  ok(defined($img->getcolorcount(maxcolors=>10000)), 'check color count is small enough');
+  print "# color count: ".$img->getcolorcount()."\n";
+  is($img->getcolorcount(), 86, 'expected number of colors');
+  is($img->getcolorcount(maxcolors => 50), undef, 'check overflow handling');
+}
 
-ok(defined($img->getcolorcount(maxcolors=>10000)), 'check color count is small enough');
-print "# color count: ".$img->getcolorcount()."\n";
-is($img->getcolorcount(), 86, 'expected number of colors');
-is($img->getcolorcount(maxcolors => 50), undef, 'check overflow handling');
+{
+  my $black = Imager::Color->new(0, 0, 0);
+  my $blue  = Imager::Color->new(0, 0, 255);
+  my $red   = Imager::Color->new(255, 0, 0);
+  
+  my $im    = Imager->new(xsize=>50, ysize=>50);
+  
+  my $count = $im->getcolorcount();
+  is ($count, 1, "getcolorcount is 1");
+  my @colour_usage = $im->getcolorusage();
+  is_deeply (\@colour_usage, [2500], "2500 are in black");
+  
+  $im->box(filled=>1, color=>$blue, xmin=>25);
+  
+  $count = $im->getcolorcount();
+  is ($count, 2, "getcolorcount is 2");
+  @colour_usage = $im->getcolorusage();
+  is_deeply(\@colour_usage, [1250, 1250] , "1250, 1250: Black and blue");
+  
+  $im->box(filled=>1, color=>$red, ymin=>25);
+  
+  $count = $im->getcolorcount();
+  is ($count, 3, "getcolorcount is 3");
+  @colour_usage = $im->getcolorusage();
+  is_deeply(\@colour_usage, [625, 625, 1250] , 
+           "625, 625, 1250: Black blue and red");
+  @colour_usage = $im->getcolorusage(maxcolors => 2);
+  is(@colour_usage, 0, 'test overflow check');
+  
+  my $colour_usage = $im->getcolorusagehash();
+  my $red_pack = pack("CCC", 255, 0, 0);
+  my $blue_pack = pack("CCC", 0, 0, 255);
+  my $black_pack = pack("CCC", 0, 0, 0);
+  is_deeply( $colour_usage, 
+            { $black_pack => 625, $blue_pack => 625, $red_pack => 1250 },
+            "625, 625, 1250: Black blue and red (hash)");
+  is($im->getcolorusagehash(maxcolors => 2), undef,
+     'test overflow check');
+
+  # test with a greyscale image
+  my $im_g = $im->convert(preset => 'grey');
+  # since the grey preset scales each source channel differently
+  # each of the original colors will be converted to different colors
+  is($im_g->getcolorcount, 3, '3 colors (grey)');
+  is_deeply([ $im_g->getcolorusage ], [ 625, 625, 1250 ], 
+           'color counts (grey)');
+  is_deeply({ "\x00" => 625, "\x12" => 625, "\x38" => 1250 },
+           $im_g->getcolorusagehash,
+           'color usage hash (grey)');
+}
index d12d02c..2ff91d8 100644 (file)
@@ -25,7 +25,7 @@ my @private =
   );
 my @trustme = ( '^open$',  );
 
-plan tests => 17;
+plan tests => 18;
 
 {
   pod_coverage_ok('Imager', { also_private => \@private,
@@ -66,12 +66,15 @@ plan tests => 17;
     last if /^=head1 METHOD INDEX/;
   }
   my @indexed;
+  my @unknown_indexed;
   while (<IMAGER>) {
     last if /^=\w/;
 
     if (/^(\w+)\(/) {
       push @indexed, $1;
-      delete $methods{$1};
+      unless (delete $methods{$1}) {
+       push @unknown_indexed, $1;
+      }
     }
   }
 
@@ -79,6 +82,10 @@ plan tests => 17;
     print "# the following methods are documented but not in the index:\n";
     print "#  $_\n" for sort keys %methods;
   }
+  unless (is(@unknown_indexed, 0, "only methods in method index")) {
+    print "# the following names are in the method index but not documented\n";
+    print "#  $_\n" for sort @unknown_indexed;
+  }
 
   sub dict_cmp_func;
   is_deeply(\@indexed, [ sort dict_cmp_func @indexed ],