From: Tony Cook Date: Sat, 25 Aug 2007 10:36:11 +0000 (+0000) Subject: added cleanup, documentation, further tests and grayscale support X-Git-Tag: Imager-0.60~9 X-Git-Url: http://git.imager.perl.org/imager.git/commitdiff_plain/a60905e49753b5f89573fbf3b4136dfc87206d4e added cleanup, documentation, further tests and grayscale support --- diff --git a/Changes b/Changes index d85ecb36..4ebb29ee 100644 --- 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 diff --git a/Imager.pm b/Imager.pm index 065b29bd..a5c039cd 100644 --- 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 getcolors() - L - get colors from the image palette, if it has one +getcolorusage() - L + +getcolorusagehash() - L + get_file_limits() - L getheight() - L diff --git a/Imager.xs b/Imager.xs index 0ccba047..c59815d6 100644 --- 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 dfef965b..846126c0 100644 --- 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 diff --git a/imager.h b/imager.h index 6595267d..1c027548 100644 --- 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); diff --git a/imdatatypes.h b/imdatatypes.h index e9469e14..2c00b792 100644 --- a/imdatatypes.h +++ b/imdatatypes.h @@ -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 { diff --git a/lib/Imager/ImageTypes.pod b/lib/Imager/ImageTypes.pod index 92991115..81d8cab7 100644 --- a/lib/Imager/ImageTypes.pod +++ b/lib/Imager/ImageTypes.pod @@ -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. To get the number of channels in an image C is used. -=item getcolorcount - -It is possible to have Imager find the number of colors in an image by -with the C 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 * + +Xmaxcolors - 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 * + +Xmaxcolors - 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 * + +Xmaxcolors - 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 diff --git a/t/t90cc.t b/t/t90cc.t index 3aeef9a1..253db3f2 100644 --- 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)'); +} diff --git a/t/t93podcover.t b/t/t93podcover.t index d12d02cb..2ff91d84 100644 --- a/t/t93podcover.t +++ b/t/t93podcover.t @@ -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 () { 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 ],