File Coverage

blib/lib/Acme/Colour/Fuzzy.pm
Criterion Covered Total %
statement 15 63 23.8
branch 0 6 0.0
condition 0 8 0.0
subroutine 5 8 62.5
pod 3 3 100.0
total 23 88 26.1


line stmt bran cond sub pod time code
1             package Acme::Colour::Fuzzy;
2              
3             =head1 NAME
4              
5             Acme::Colour::Fuzzy - give names to arbitrary RGB triplets
6              
7             =head1 SYNOPSIS
8              
9             # specify colour set, default is VACCC
10             my $fuzzy = Acme::Colour::Fuzzy->new( 'VACCC' );
11              
12             # list of similar colours, sorted by similarity
13             my @approximations = $fuzzy->colour_approximations( $r, $g, $b, $count );
14              
15             # made-up name for the colour
16             my $name = $fuzzy->colour_name( $r, $g, $b );
17              
18             =head1 DESCRIPTION
19              
20             This module uses sophisticated colour-distance metrics and some
21             made-up computations to give a likely name to an arbitrary RGB
22             triplet.
23              
24             =cut
25              
26 2     2   8263 use strict;
  2         5  
  2         97  
27 2     2   14 use base qw(Class::Accessor::Fast);
  2         4  
  2         2119  
28              
29             our $VERSION = '0.02';
30              
31 2     2   9356 use Graphics::ColorNames qw(hex2tuple);
  2         25394  
  2         142  
32 2     2   1893 use Color::Similarity;
  2         421  
  2         56  
33 2     2   12 use List::Util qw(max);
  2         4  
  2         1396  
34              
35             __PACKAGE__->mk_ro_accessors( qw(scheme colours) );
36              
37             =head1 METHODS
38              
39             =head2 new
40              
41             my $fuzzy = Acme::Colour::Fuzzy->new( $colour_set );
42              
43             Creates a new C object using the specified colour
44             set. The coolour set can be any backend for C
45             with 'VACCC' as default.
46              
47             =cut
48              
49             sub new {
50 0     0 1   my( $class, $scheme, $distance ) = @_;
51 0   0       $scheme ||= 'VACCC';
52 0   0       $distance ||= 'Color::Similarity::HCL';
53              
54 0           my $similarity = Color::Similarity->new( $distance );
55              
56             # remove duplicates, favour longer names
57 0           tie my %name2rgb, 'Graphics::ColorNames', $scheme;
58 0           my %rgb2name;
59 0           while( my( $nname, $rgb ) = each %name2rgb ) {
60 0   0       my $cname = $rgb2name{$rgb} || '';
61 0           my( $lnname, $lcname ) = ( length( $nname ), length( $cname ) );
62 0 0         if( $lnname > $lcname ) {
63 0           $rgb2name{$rgb} = $nname;
64             }
65             }
66 0           my %unique = reverse %rgb2name;
67              
68 0           my $self = $class->SUPER::new( { scheme => $scheme,
69             colours => \%unique,
70             distance => $similarity,
71             } );
72              
73 0           return $self;
74             }
75              
76             =head2 colour_approximations
77              
78             my @approximations = $fuzzy->colour_approximations( $r, $g, $b, $count );
79              
80             Returns a list of at most C<$count> colours similar to the given
81             one. Each element of the list is an hash with the following structure:
82              
83             { name => 'Red', # name taken from Graphics::ColourNames
84             distance => 7.1234567,
85             rgb => [ 255, 0, 0 ],
86             }
87              
88             =cut
89              
90             sub colour_approximations {
91 0     0 1   my( $self, $ir, $ig, $ib, $count ) = @_;
92 0           my $cdist = $self->{distance};
93 0           my $ic = $cdist->convert_rgb( $ir, $ig, $ib );
94              
95 0           my @res;
96 0           while( my( $name, $rgb ) = each %{$self->colours} ) {
  0            
97 0           my( $nr, $ng, $nb ) = hex2tuple( $rgb );
98 0           my $nc = $cdist->convert_rgb( $nr, $ng, $nb );
99              
100 0           my $dist = $cdist->distance( $ic, $nc );
101 0           push @res, { distance => $dist,
102             name => $name,
103             rgb => [ $nr, $ng, $nb ],
104             };
105             }
106 0           @res = sort { $a->{distance} <=> $b->{distance} } @res;
  0            
107              
108 0   0       return @res[ 0 .. ( $count || 20 ) - 1 ];
109             }
110              
111             =head2 colour_name
112              
113             my $name = $fuzzy->colour_name( $r, $g, $b );
114              
115             Makes up a colour name using the data computed by C.
116              
117             =cut
118              
119             sub colour_name {
120 0     0 1   my( $self, $ir, $ig, $ib ) = @_;
121 0           my @similar = $self->colour_approximations( $ir, $ig, $ib );
122 0           my %words;
123              
124             # FIXME use some real metric, not made-up computations
125 0           my $max_distance = $similar[-1]{distance};
126 0           my $pivot = max( ( $max_distance * 9 / 13 ), 1 );
127 0           foreach my $similar ( @similar ) {
128 0 0         my @words = map { /^(dark)(\w+)/ ? ( $1, $2 ) : ( $_ ) }
  0            
129 0           map { s/\d+//; $_ } # remove numbers
  0            
130             split /[ \-]+/, $similar->{name};
131 0           my $weight = ( $pivot - $similar->{distance} ) / $pivot;
132 0           foreach( @words ) {
133 0           $words{$_} += $weight;
134             }
135             }
136 0           my @weights = sort { $b->[1] <=> $a->[1] }
  0            
137             map [ $_ => $words{$_} ],
138             keys %words;
139              
140 0           my @names;
141 0           my $first_weight = $weights[0][1];
142 0           foreach my $weight ( @weights ) {
143 0 0         last if $weight->[1] < $first_weight / 3;
144 0           push @names, $weight->[0];
145             }
146              
147 0           return join ' ', reverse @names;
148             }
149              
150             =head1 SEE ALSO
151              
152             L
153              
154             =head1 AUTHOR
155              
156             Mattia Barbon, C<< >>
157              
158             =head1 COPYRIGHT
159              
160             Copyright (C) 2007, Mattia Barbon
161              
162             This program is free software; you can redistribute it or modify it
163             under the same terms as Perl itself.
164              
165             =cut
166              
167             1;