- 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
# 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
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>
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);
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;
}
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));
* (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,
* 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
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);
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 {
=back
-=head2 Getting Information About an Imager Object
+=head2 Image Attribute functions
+
+These return basic attributes of an image object.
=over
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
=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
#!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)');
+}
);
my @trustme = ( '^open$', );
-plan tests => 17;
+plan tests => 18;
{
pod_coverage_ok('Imager', { also_private => \@private,
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;
+ }
}
}
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 ],