File Coverage

blib/lib/Convert/Color.pm
Criterion Covered Total %
statement 93 116 80.1
branch 22 42 52.3
condition 1 8 12.5
subroutine 21 24 87.5
pod 5 7 71.4
total 142 197 72.0


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2009-2022 -- leonerd@leonerd.org.uk
5              
6             package Convert::Color 0.13;
7              
8 18     18   67969 use v5.14;
  18         72  
9 18     18   100 use warnings;
  18         34  
  18         464  
10              
11 18     18   91 use Carp;
  18         35  
  18         1125  
12              
13 18     18   9101 use List::UtilsBy qw( min_by );
  18         35246  
  18         1264  
14              
15 18         145 use Module::Pluggable require => 0,
16 18     18   8456 search_path => [ 'Convert::Color' ];
  18         197485  
17             my @plugins = Convert::Color->plugins;
18              
19             =head1 NAME
20              
21             C - color space conversions and named lookups
22              
23             =head1 SYNOPSIS
24              
25             use Convert::Color;
26              
27             my $color = Convert::Color->new( 'hsv:76,0.43,0.89' );
28              
29             my ( $red, $green, $blue ) = $color->rgb;
30              
31             # GTK uses 16-bit values
32             my $gtk_col = Gtk2::Gdk::Color->new( $color->as_rgb16->rgb16 );
33              
34             # HTML uses #rrggbb in hex
35             my $html = '';
36              
37             =head1 DESCRIPTION
38              
39             This module provides conversions between commonly used ways to express colors.
40             It provides conversions between color spaces such as RGB and HSV, and it
41             provides ways to look up colors by a name.
42              
43             This class provides a base for subclasses which represent particular color
44             values in particular spaces. The base class provides methods to represent the
45             color in a few convenient forms, though subclasses may provide more specific
46             details for the space in question.
47              
48             For more detail, read the documentation on these classes; namely:
49              
50             =over 4
51              
52             =item *
53              
54             L - red/green/blue as floats between 0 and 1
55              
56             =item *
57              
58             L - red/green/blue as 8-bit integers
59              
60             =item *
61              
62             L - red/green/blue as 16-bit integers
63              
64             =item *
65              
66             L - hue/saturation/value
67              
68             =item *
69              
70             L - hue/saturation/lightness
71              
72             =item *
73              
74             L - cyan/magenta/yellow
75              
76             =item *
77              
78             L - cyan/magenta/yellow/key (blackness)
79              
80             =back
81              
82             The following classes are subclasses of one of the above, which provide a way
83             to access predefined colors by names:
84              
85             =over 4
86              
87             =item *
88              
89             L - named lookup for the basic VGA colors
90              
91             =item *
92              
93             L - named lookup of colors from X11's F
94              
95             =back
96              
97             =cut
98              
99             =head1 CONSTRUCTOR
100              
101             =cut
102              
103             my $_space2class_cache_initialised;
104             my %_space2class_cache; # {$space} = $class
105             my %_class2space_cache; # {$class} = $space
106              
107             # doc'ed later for readability...
108             sub register_color_space
109             {
110 105     105 1 291 my $class = shift;
111 105         304 my ( $space ) = @_;
112              
113 105 50       368 exists $_space2class_cache{$space} and croak "Color space $space is already defined";
114 105 50       297 exists $_class2space_cache{$class} and croak "Class $class already declared a color space";
115              
116 105         245 $_space2class_cache{$space} = $class;
117 105         191 $_class2space_cache{$class} = $space;
118              
119 18     18   3589 no strict 'refs';
  18         43  
  18         15772  
120 105     83   512 *{"as_$space"} = sub { shift->convert_to( $space ) };
  105         1120  
  83         298  
121             }
122              
123             sub _space2class
124             {
125 129     129   221 my ( $space ) = @_;
126              
127 129 100       310 unless( $_space2class_cache_initialised ) {
128 10         34 $_space2class_cache_initialised++;
129             # Initialise the space name to class cache
130 10         36 foreach my $class ( @plugins ) {
131 100         544 ( my $file = "$class.pm" ) =~ s{::}{/}g;
132 100 50       32347 require $file or next;
133              
134 100 50       518 $class->can( 'COLOR_SPACE' ) or next;
135 0 0       0 my $thisspace = $class->COLOR_SPACE or next;
136              
137 0         0 warnings::warn( deprecated => "Discovered $class by deprecated COLOR_SPACE method" );
138              
139 0         0 $class->register_color_space( $thisspace );
140             }
141             }
142              
143 129         409 return $_space2class_cache{$space};
144             }
145              
146             =head2 new
147              
148             $color = Convert::Color->new( STRING )
149              
150             Return a new value to represent the color specified by the string. This string
151             should be prefixed by the name of the color space to which it applies. For
152             example
153              
154             rgb:RED,GREEN,BLUE
155             rgb8:RRGGBB
156             rgb16:RRRRGGGGBBBB
157             hsv:HUE,SAT,VAL
158             hsl:HUE,SAT,LUM
159             cmy:CYAN,MAGENTA,YELLOW
160             cmyk:CYAN,MAGENTA,YELLOW,KEY
161              
162             vga:NAME
163             vga:INDEX
164              
165             x11:NAME
166              
167             For more detail, see the constructor of the color space subclass in question.
168              
169             =cut
170              
171             sub new
172             {
173 8     8 1 98 shift;
174 8         20 my ( $str ) = @_;
175              
176 8 50       55 $str =~ m/^(\w+):(.*)$/ or croak "Unable to parse color name $str";
177 8         31 ( my $space, $str ) = ( $1, $2 );
178              
179 8 50       16 my $class = _space2class( $space ) or croak "Unrecognised color space name '$space'";
180              
181 8         44 return $class->new( $str );
182             }
183              
184             =head1 METHODS
185              
186             =cut
187              
188             =head2 rgb
189              
190             ( $red, $green, $blue ) = $color->rgb
191              
192             Returns the individual red, green and blue color components of the color
193             value. For RGB values, this is done directly. For values in other spaces, this
194             is done by first converting them to an RGB value using their C
195             method.
196              
197             =cut
198              
199             sub rgb
200             {
201 0     0 1 0 my $self = shift;
202 0         0 croak "Abstract method - should be overloaded by ".ref($self);
203             }
204              
205             =head1 COLOR SPACE CONVERSIONS
206              
207             Cross-conversion between color spaces is provided by the C
208             method, assisted by helper methods in the two color space classes involved.
209              
210             When converting C<$color> from color space SRC to color space DEST, the
211             following operations are attemped, in this order. SRC and DEST refer to the
212             names of the color spaces, e.g. C.
213              
214             =over 4
215              
216             =item 1.
217              
218             If SRC and DEST are equal, return C<$color> as it stands.
219              
220             =item 2.
221              
222             If the SRC space's class provides a C method, use it.
223              
224             =item 3.
225              
226             If the DEST space's class provides a C constructor, call it and
227             pass C<$color>.
228              
229             =item 4.
230              
231             If the DEST space's class provides a C constructor, convert C<$color>
232             to red/green/blue components then call it.
233              
234             =item 5.
235              
236             If none of these operations worked, then throw an exception.
237              
238             =back
239              
240             These functions may be called in the following ways:
241              
242             $other = $color->convert_to_DEST()
243             $other = Dest::Class->new_from_SRC( $color )
244             $other = Dest::Class->new_rgb( $color->rgb )
245              
246             =cut
247              
248             =head2 convert_to
249              
250             $other = $color->convert_to( $space )
251              
252             Attempt to convert the color into its representation in the given space. See
253             above for the various ways this may be achieved.
254              
255             If the relevant subclass has already been loaded (either explicitly, or
256             implicitly by either the C or C methods), then a specific
257             conversion method will be installed in the class.
258              
259             $other = $color->as_$space
260              
261             Methods of this form are currently Ced if they do not yet exist, but
262             this feature should not be relied upon - see below.
263              
264             =cut
265              
266             sub convert_to
267             {
268 121     121 1 268 my $self = shift;
269 121         238 my ( $to_space ) = @_;
270              
271 121 50       233 my $to_class = _space2class( $to_space ) or croak "Unrecognised color space name '$to_space'";
272              
273 121         282 my $from_space = $_class2space_cache{ref $self};
274              
275 121 100       288 if( $from_space eq $to_space ) {
276             # Identity conversion
277 42         131 return $self;
278             }
279              
280 79         116 my $code;
281 79 100       247 if( $code = $self->can( "convert_to_$to_space" ) ) {
    100          
    50          
282 30         69 return $code->( $self );
283             }
284             elsif( $code = $to_class->can( "new_from_$from_space" ) ) {
285 3         11 return $code->( $to_class, $self );
286             }
287             elsif( $code = $to_class->can( "new_rgb" ) ) {
288             # TODO: check that $self->rgb is overloaded
289 46         135 return $code->( $to_class, $self->rgb );
290             }
291             else {
292 0         0 croak "Cannot convert from space '$from_space' to space '$to_space'";
293             }
294             }
295              
296             # Fallback implementations in case subclasses don't provide anything better
297              
298             sub convert_to_rgb
299             {
300 28     28 0 44 my $self = shift;
301 28         138 require Convert::Color::RGB;
302 28         142 return Convert::Color::RGB->new( $self->rgb );
303             }
304              
305             =head1 AUTOLOADED CONVERSION METHODS
306              
307             This class provides C and C behaviour which automatically
308             constructs conversion methods. The following method calls are identical:
309              
310             $color->convert_to('rgb')
311             $color->as_rgb
312              
313             The generated method will be stored in the package, so that future calls will
314             not have the AUTOLOAD overhead.
315              
316             This feature is deprecated and should not be relied upon, due to the delicate
317             nature of C.
318              
319             =cut
320              
321             # Since this is AUTOLOADed, we can dynamically provide new methods for classes
322             # discovered at runtime.
323              
324             sub can
325             {
326 274     274 0 457 my $self = shift;
327 274         480 my ( $method ) = @_;
328              
329 274 50       656 if( $method =~ m/^as_(.*)$/ ) {
330 0         0 my $to_space = $1;
331 0 0       0 _space2class( $to_space ) or return undef;
332              
333             return sub {
334 0     0   0 my $self = shift;
335 0         0 return $self->convert_to( $to_space );
336 0         0 };
337             }
338              
339 274         1668 return $self->SUPER::can( $method );
340             }
341              
342             sub AUTOLOAD
343             {
344 197     197   13711 my ( $method ) = our $AUTOLOAD =~ m/::([^:]+)$/;
345              
346 197 50       3069 return if $method eq "DESTROY";
347              
348 0 0 0     0 if( ref $_[0] and my $code = $_[0]->can( $method ) ) {
349             # It's possible that the lazy loading by ->can has just created this method
350 0         0 warnings::warn( deprecated => "Relying on AUTOLOAD to provide $method" );
351 18     18   158 no strict 'refs';
  18         48  
  18         4498  
352 0 0       0 unless( defined &{$method} ) {
  0         0  
353 0         0 *{$method} = $code;
  0         0  
354             }
355 0         0 goto &$code;
356             }
357              
358 0   0     0 my $class = ref $_[0] || $_[0];
359 0         0 croak qq(Cannot locate object method "$method" via package "$class");
360             }
361              
362             =head1 OTHER METHODS
363              
364             As well as the above, it is likely the subclass will provide accessors to
365             directly obtain the components of its representation in the specific space.
366             For more detail, see the documentation for the specific subclass in question.
367              
368             =cut
369              
370             =head1 SUBCLASS METHODS
371              
372             This base class is intended to be subclassed to provide more color spaces.
373              
374             =cut
375              
376             =head2 register_color_space
377              
378             $class->register_color_space( $space )
379              
380             A subclass should call this method to register itself as a named color space.
381              
382             =cut
383              
384             =head2 register_palette
385              
386             $class->register_palette( %args )
387              
388             A subclass that provides a fixed set of color values should call this method,
389             to set up automatic conversions that look for the closest match within the
390             set. This conversion process is controlled by the C<%args>:
391              
392             =over 8
393              
394             =item enumerate => STRING or CODE
395              
396             A method name or anonymous CODE reference which will be used to generate the
397             list of color values.
398              
399             =item enumerate_once => STRING or CODE
400              
401             As per C, but will be called only once and the results cached.
402              
403             =back
404              
405             This method creates a new class method on the calling package, called
406             C.
407              
408             =head2 closest_to
409              
410             $color = $pkg->closest_to( $orig, $space )
411              
412             Returns the color in the space closest to the given value. The distance is
413             measured in the named space; defaulting to C if this is not provided.
414              
415             In the case of a tie, where two or more colors have the same distance from the
416             target, the first one will be chosen.
417              
418             =cut
419              
420             sub register_palette
421             {
422 21     21 1 57 my $pkg = shift;
423 21         72 my %args = @_;
424              
425 21         36 my $enumerate;
426              
427 21 100       93 if( $args{enumerate} ) {
    50          
428 11         33 $enumerate = $args{enumerate};
429             }
430             elsif( my $enumerate_once = $args{enumerate_once} ) {
431 10         21 my @colors;
432             $enumerate = sub {
433 1     1   3 my $class = shift;
434 1 50       6 @colors = $class->$enumerate_once unless @colors;
435 1         13 return @colors;
436             }
437 10         49 }
438             else {
439 0         0 croak "Require 'enumerate' or 'enumerate_once'";
440             }
441              
442 18     18   139 no strict 'refs';
  18         46  
  18         5629  
443              
444 21         173 *{"${pkg}::closest_to"} = sub {
445 1     1   2 my $class = shift;
446 1         4 my ( $orig, $space ) = @_;
447              
448 1   50     4 $space ||= "rgb";
449              
450 1         5 $orig = $orig->convert_to( $space );
451 1         5 my $dst = "dst_${space}_cheap";
452              
453 1     8   7 return min_by { $orig->$dst( $_->convert_to( $space ) ) } $class->$enumerate;
  8         59  
454 21         102 };
455              
456 21         75 foreach my $space (qw( rgb hsv hsl )) {
457 63         333 *{"${pkg}::new_from_${space}"} = sub {
458 1     1   2 my $class = shift;
459 1         3 my ( $rgb ) = @_;
460 1         4 return $pkg->closest_to( $rgb, $space );
461 63         200 };
462             }
463              
464 21         120 *{"${pkg}::new_rgb"} = sub {
465 0     0     my $class = shift;
466 0           return $class->closest_to( Convert::Color::RGB->new( @_ ), "rgb" );
467 21         85 };
468             }
469              
470             =head1 AUTHOR
471              
472             Paul Evans
473              
474             =cut
475              
476             0x55AA;