File Coverage

blib/lib/Color/RGB/Util.pm
Criterion Covered Total %
statement 262 311 84.2
branch 115 176 65.3
condition 30 61 49.1
subroutine 30 32 93.7
pod 25 25 100.0
total 462 605 76.3


line stmt bran cond sub pod time code
1             package Color::RGB::Util;
2              
3 2     2   588994 use 5.010001;
  2         9  
4 2     2   13 use strict;
  2         11  
  2         62  
5 2     2   16 use warnings;
  2         3  
  2         152  
6              
7             #use List::Util qw(min);
8              
9 2     2   12 use Exporter qw(import);
  2         6  
  2         12729  
10              
11             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
12             our $DATE = '2025-03-19'; # DATE
13             our $DIST = 'Color-RGB-Util'; # DIST
14             our $VERSION = '0.609'; # VERSION
15              
16             our @EXPORT_OK = qw(
17             assign_rgb_color
18             assign_rgb_dark_color
19             assign_rgb_light_color
20             hsl2hsv
21             hsl2rgb
22             hsv2hsl
23             hsv2rgb
24             int2rgb
25             mix_2_rgb_colors
26             mix_rgb_colors
27             rand_rgb_color
28             rand_rgb_colors
29             reverse_rgb_color
30             rgb2grayscale
31             rgb2hsv
32             rgb2hsl
33             rgb2int
34             rgb2sepia
35             rgb_diff
36             rgb_distance
37             rgb_is_dark
38             rgb_is_light
39             rgb_luminance
40             tint_rgb_color
41             rgb_closest_to
42             );
43              
44             our %SPEC;
45              
46             my $re_rgb = qr/\A#?([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})\z/;
47              
48             sub _min {
49 0 0   0   0 $_[0] < $_[1] ? $_[0] : $_[1];
50             }
51              
52             sub _wrap_h {
53 12     12   490452 my $h = shift;
54 12 100       49 $h %= 360 if abs($h) > 360;
55 12 100       66 $h >= 0 ? $h : 360+$h;
56             }
57              
58             sub assign_rgb_color {
59 9     9 1 6258 require Digest::SHA;
60              
61 9         12442 my ($str) = @_;
62              
63 9         90 my $sha1 = Digest::SHA::sha1_hex($str);
64 9         68 substr($sha1, 0, 2) .
65             substr($sha1, 18, 2) .
66             substr($sha1, 38, 2);
67             }
68              
69             sub assign_rgb_dark_color {
70 3     3 1 5291 my $str = shift;
71              
72 3         10 my $rgb = assign_rgb_color($str);
73 3 100       12 rgb_is_dark($rgb) ? $rgb : mix_2_rgb_colors($rgb, '000000');
74             }
75              
76             sub assign_rgb_light_color {
77 3     3 1 5695 my $str = shift;
78              
79 3         11 my $rgb = assign_rgb_color($str);
80 3 100       12 rgb_is_light($rgb) ? $rgb : mix_2_rgb_colors($rgb, 'ffffff');
81             }
82              
83             sub int2rgb {
84 5     5 1 2901 my $int = shift;
85              
86 5         31 return sprintf("%02x%02x%02x",
87             ($int & 0xff0000) >> 16,
88             ($int & 0x00ff00) >> 8,
89             ($int & 0x0000ff),
90             );
91             }
92              
93             sub mix_2_rgb_colors {
94 9     9 1 3372 my ($rgb1, $rgb2, $pct) = @_;
95              
96 9   100     53 $pct //= 0.5;
97              
98 9 100       93 my ($r1, $g1, $b1) =
99             $rgb1 =~ $re_rgb or die "Invalid rgb1 color '$rgb1', must be in 'ffffff' form";
100 8 50       44 my ($r2, $g2, $b2) =
101             $rgb2 =~ $re_rgb or die "Invalid rgb2 color '$rgb2', must be in 'ffffff' form";
102 8         18 for ($r1, $g1, $b1, $r2, $g2, $b2) { $_ = hex $_ }
  48         65  
103              
104 8         81 return sprintf("%02x%02x%02x",
105             $r1 + $pct*($r2-$r1),
106             $g1 + $pct*($g2-$g1),
107             $b1 + $pct*($b2-$b1),
108             );
109             }
110              
111             sub mix_rgb_colors {
112              
113 6     6 1 6145 my (@weights, @r, @g, @b);
114              
115 6         37 while (@_ >= 2) {
116 10         30 my ($rgb, $weight) = splice @_, 0, 2;
117 10 100       108 my ($r, $g, $b) = $rgb =~ $re_rgb
118             or die "Invalid rgb color '$rgb', must be in 'ffffff' form";
119 9         24 push @r, hex $r;
120 9         17 push @g, hex $g;
121 9         17 push @b, hex $b;
122 9         27 push @weights, $weight;
123             }
124 5         10 my $tot_r = 0; for (0..$#r) { $tot_r += $r[$_]*$weights[$_] }
  5         16  
  9         23  
125 5         10 my $tot_g = 0; for (0..$#g) { $tot_g += $g[$_]*$weights[$_] }
  5         11  
  9         19  
126 5         8 my $tot_b = 0; for (0..$#b) { $tot_b += $b[$_]*$weights[$_] }
  5         38  
  9         16  
127 5         10 my $tot_weight = 0; $tot_weight += $_ for @weights;
  5         46  
128 5 100       34 die "Zero/negative total weight" unless $tot_weight > 0;
129              
130 3         36 return sprintf("%02x%02x%02x",
131             $tot_r / $tot_weight,
132             $tot_g / $tot_weight,
133             $tot_b / $tot_weight,
134             );
135             }
136              
137             $SPEC{rand_rgb_color} = {
138             v => 1.1,
139             summary => 'Generate a random RGB color',
140             args_as => 'array',
141             args => {
142             from_color => {
143             schema => 'color::rgb24',
144             default => '000000',
145             pos => 0,
146             },
147             to_color => {
148             schema => 'color::rgb24',
149             default => 'ffffff',
150             pos => 1,
151             },
152             },
153             result_naked => 1,
154             result => {
155             schema => 'color::rgb24*',
156             },
157             };
158             sub rand_rgb_color {
159 101     101 1 4406 my ($rgb1, $rgb2) = @_;
160              
161 101   50     296 $rgb1 //= '000000';
162 101 50       426 my ($r1, $g1, $b1) =
163             $rgb1 =~ $re_rgb or die "Invalid rgb1 color '$rgb1', must be in 'ffffff' form";
164 101   50     291 $rgb2 //= 'ffffff';
165 101 50       384 my ($r2, $g2, $b2) =
166             $rgb2 =~ $re_rgb or die "Invalid rgb2 color '$rgb2', must be in 'ffffff' form";
167 101         184 for ($r1, $g1, $b1, $r2, $g2, $b2) { $_ = hex $_ }
  606         719  
168              
169 101         477 return sprintf("%02x%02x%02x",
170             $r1 + rand()*($r2-$r1+1),
171             $g1 + rand()*($g2-$g1+1),
172             $b1 + rand()*($b2-$b1+1),
173             );
174             }
175              
176             sub rand_rgb_colors {
177 31 50   31 1 5115 my $opts = ref $_[0] eq 'HASH' ? shift : {};
178 31   100     71 my $num = shift // 1;
179 31 50       51 my $light_color = exists($opts->{light_color}) ? $opts->{light_color} : 1;
180 31   50     98 my $max_attempts = $opts->{max_attempts} // 1000;
181 31         38 my $avoid_colors = $opts->{avoid_colors};
182 31         35 my $hash_prefix = $opts->{hash_prefix};
183 31         32 my $from_color = $opts->{from_color};
184 31         34 my $to_color = $opts->{to_color};
185              
186 31         30 my $num_check = 10;
187 31         43 my $min_distance = rgb_diff("000000", "ffffff", "approx2") / 2 / $num;
188              
189 31         36 my @res;
190 31         51 while (@res < $num) {
191 35         33 my $num_attempts = 0;
192 35         34 my $rgb;
193 35         27 while (1) {
194 71         86 $rgb = rand_rgb_color($from_color, $to_color);
195 71         76 my $reject = 0;
196             REJECT: {
197 71 50       80 if ($light_color) {
  71 0       85  
198 71 100       86 do { $reject++; last } if rgb_is_dark($rgb);
  36         38  
  36         42  
199             } elsif (defined $light_color) {
200 0 0       0 do { $reject++; last } if rgb_is_light($rgb);
  0         0  
  0         0  
201             }
202 35 50 33     68 if ($avoid_colors && ref $avoid_colors eq 'ARRAY') {
203 0 0       0 do { $reject++; last } if grep { $rgb eq $_ } @$avoid_colors;
  0         0  
  0         0  
  0         0  
204             }
205 35 50 33     54 if ($avoid_colors && ref $avoid_colors eq 'HASH') {
206 0 0       0 do { $reject++; last } if $avoid_colors->{$rgb}
  0         0  
  0         0  
207             }
208              
209 35         55 for (1..$num_check) {
210 45 100       81 last if @res-$_ < 0;
211 10         13 my $prev_rgb = $res[ @res - $_ ];
212 10 50       14 do { $reject++; last REJECT } if rgb_diff($rgb, $prev_rgb, "approx2") < $min_distance;
  0         0  
  0         0  
213             }
214              
215             } # REJECT
216 71 100       119 last if !$reject;
217 36 50       61 last if ++$num_attempts >= $max_attempts;
218             }
219 35 50       93 push @res, ($hash_prefix ? "#" : "") . $rgb;
220             }
221 31         101 @res;
222             }
223              
224             sub reverse_rgb_color {
225 1     1 1 4389 my ($rgb) = @_;
226              
227 1 50       14 my ($r, $g, $b) =
228             $rgb =~ $re_rgb or die "Invalid rgb color '$rgb', must be in 'ffffff' form";
229 1         3 for ($r, $g, $b) { $_ = hex $_ }
  3         6  
230              
231 1         9 return sprintf("%02x%02x%02x", 255-$r, 255-$g, 255-$b);
232             }
233              
234             sub rgb2grayscale {
235 3     3 1 3359 my ($rgb, $algo) = @_;
236              
237 3 50       31 my ($r, $g, $b) =
238             $rgb =~ $re_rgb or die "Invalid rgb color '$rgb', must be in 'ffffff' form";
239 3         8 for ($r, $g, $b) { $_ = hex $_ }
  9         14  
240              
241 3   100     12 $algo //= 'average';
242 3 100       7 if ($algo eq 'weighted_average') {
    100          
243 1         5 my $avg = int(0.299*$r + 0.587*$g + 0.114*$b);
244 1         7 return sprintf("%02x%02x%02x", $avg, $avg, $avg);
245             } elsif ($algo eq 'average') {
246 1         4 my $avg = int(($r + $g + $b)/3);
247 1         8 return sprintf("%02x%02x%02x", $avg, $avg, $avg);
248             } else {
249 1         11 die "Unknown algo '$algo'";
250             }
251             }
252              
253             sub rgb2int {
254 65     65 1 3087 my $rgb = shift;
255              
256             # just to check
257 65 50       316 $rgb =~ $re_rgb or die "Invalid rgb color '$rgb', must be in 'ffffff' form";
258              
259 65         171 hex($rgb);
260             }
261              
262             sub rgb2sepia {
263 1     1 1 2975 my ($rgb) = @_;
264              
265 1 50       13 my ($r, $g, $b) =
266             $rgb =~ $re_rgb or die "Invalid rgb color '$rgb', must be in 'ffffff' form";
267 1         3 for ($r, $g, $b) { $_ = hex $_ }
  3         5  
268              
269             # reference: http://www.techrepublic.com/blog/howdoi/how-do-i-convert-images-to-grayscale-and-sepia-tone-using-c/120
270 1         4 my $or = ($r*0.393) + ($g*0.769) + ($b*0.189);
271 1         5 my $og = ($r*0.349) + ($g*0.686) + ($b*0.168);
272 1         3 my $ob = ($r*0.272) + ($g*0.534) + ($b*0.131);
273 1 50       2 for ($or, $og, $ob) { $_ = 255 if $_ > 255 }
  3         6  
274 1         8 return sprintf("%02x%02x%02x", $or, $og, $ob);
275             }
276              
277             sub rgb_diff {
278 45     45 1 2821 my ($rgb1, $rgb2, $algo) = @_;
279              
280 45   50     69 $algo //= 'euclidean';
281              
282 45 50       199 my ($r1, $g1, $b1) =
283             $rgb1 =~ $re_rgb or die "Invalid rgb1 color '$rgb1', must be in 'ffffff' form";
284 45 50       165 my ($r2, $g2, $b2) =
285             $rgb2 =~ $re_rgb or die "Invalid rgb2 color '$rgb2', must be in 'ffffff' form";
286 45         67 for ($r1, $g1, $b1, $r2, $g2, $b2) { $_ = hex $_ }
  270         299  
287              
288 45         75 my $dr2 = ($r1-$r2)**2;
289 45         51 my $dg2 = ($g1-$g2)**2;
290 45         50 my $db2 = ($b1-$b2)**2;
291              
292 45 100 100     114 if ($algo eq 'approx1' || $algo eq 'approx2') {
    50 33        
    50          
293 44         62 my $rm = ($r1 + $r2)/2;
294 44 100       80 if ($algo eq 'approx1') {
295 3         25 return (2*$dr2 + 4*$dg2 + 3*$db2 + $rm*($dr2 - $db2)/256 )**0.5;
296             } else { # approx2
297 41 100       58 if ($rm < 128) {
298 34         85 return (3*$dr2 + 4*$dg2 + 2*$db2)**0.5;
299             } else {
300 7         24 return (2*$dr2 + 4*$dg2 + 3*$db2)**0.5;
301             }
302             }
303             } elsif ($algo eq 'hsv_euclidean' || $algo eq 'hsv_hue1') {
304 0         0 my $hsv1 = rgb2hsv($rgb1);
305 0         0 my ($h1, $s1, $v1) = split / /, $hsv1;
306 0         0 my $hsv2 = rgb2hsv($rgb2);
307 0         0 my ($h2, $s2, $v2) = split / /, $hsv2;
308              
309 0         0 my $dh2 = ( _min(abs($h2-$h1), 360-abs($h2-$h1))/180 )**2;
310 0         0 my $ds2 = ( $s2-$s1 )**2;
311 0         0 my $dv2 = ( ($v2-$v1)/255.0 )**2;
312              
313 0 0       0 if ($algo eq 'hsv_hue1') {
314 0         0 return (5*$dh2 + $ds2 + $dv2)**0.5;
315             } else { # hsv_euclidean
316 0         0 return ($dh2 + $ds2 + $dv2)**0.5;
317             }
318             } elsif ($algo eq 'euclidean') {
319 0         0 return ($dr2 + $dg2 + $db2)**0.5;
320             } else {
321 1         13 die "Unknown algo '$algo'";
322             }
323             }
324              
325             sub rgb_distance {
326 173     173 1 2965 my ($rgb1, $rgb2) = @_;
327              
328 173 50       871 my ($r1, $g1, $b1) =
329             $rgb1 =~ $re_rgb or die "Invalid rgb1 color '$rgb1', must be in 'ffffff' form";
330 173 50       563 my ($r2, $g2, $b2) =
331             $rgb2 =~ $re_rgb or die "Invalid rgb2 color '$rgb2', must be in 'ffffff' form";
332 173         268 for ($r1, $g1, $b1, $r2, $g2, $b2) { $_ = hex $_ }
  1038         1108  
333              
334 173         543 (($r1-$r2)**2 + ($g1-$g2)**2 + ($b1-$b2)**2)**0.5;
335             }
336              
337             sub rgb_is_dark {
338 78     78 1 3026 my ($rgb) = @_;
339 78 100       360 rgb_distance($rgb, "000000") < rgb_distance($rgb, "ffffff") ? 1:0;
340             }
341              
342             sub rgb_is_light {
343 7     7 1 2571 my ($rgb) = @_;
344 7 100       49 rgb_distance($rgb, "000000") > rgb_distance($rgb, "ffffff") ? 1:0;
345             }
346              
347             sub _rgb_luminance {
348 8     8   15 my ($r, $g, $b) = @_;
349 8         37 0.2126*$r/255 + 0.7152*$g/255 + 0.0722*$b/255;
350             }
351              
352             sub rgb_luminance {
353 3     3 1 2825 my ($rgb) = @_;
354              
355 3 50       29 my ($r, $g, $b) =
356             $rgb =~ $re_rgb or die "Invalid rgb color '$rgb', must be in 'ffffff' form";
357 3         8 for ($r, $g, $b) { $_ = hex $_ }
  9         11  
358              
359 3         9 return _rgb_luminance($r, $g, $b);
360             }
361              
362             sub tint_rgb_color {
363 5     5 1 3052 my ($rgb1, $rgb2, $pct) = @_;
364              
365 5   100     20 $pct //= 0.5;
366              
367 5 50       52 my ($r1, $g1, $b1) =
368             $rgb1 =~ $re_rgb or die "Invalid rgb1 color '$rgb1', must be in 'ffffff' form";
369 5 50       42 my ($r2, $g2, $b2) =
370             $rgb2 =~ $re_rgb or die "Invalid rgb2 color '$rgb2', must be in 'ffffff' form";
371 5         12 for ($r1, $g1, $b1, $r2, $g2, $b2) { $_ = hex $_ }
  30         42  
372              
373 5         12 my $lum = _rgb_luminance($r1, $g1, $b1);
374              
375 5         64 return sprintf("%02x%02x%02x",
376             $r1 + $pct*($r2-$r1)*$lum,
377             $g1 + $pct*($g2-$g1)*$lum,
378             $b1 + $pct*($b2-$b1)*$lum,
379             );
380             }
381              
382             sub rgb2hsl {
383 3     3 1 2580 my ($rgb) = @_;
384              
385 3 50       29 my ($r, $g, $b) =
386             $rgb =~ $re_rgb or die "Invalid rgb color '$rgb', must be in 'ffffff' form";
387 3         7 for ($r, $g, $b) { $_ = hex($_)/255 }
  9         15  
388              
389 3         5 my $max = $r;
390 3         5 my $maxc = 'r';
391 3         4 my $min = $r;
392              
393 3 100       7 if ($g > $max) {
394 1         2 $max = $g;
395 1         2 $maxc = 'g';
396             }
397 3 100       32 if ($b > $max) {
398 1         2 $max = $b;
399 1         4 $maxc = 'b';
400             }
401              
402 3 100       6 if ($g < $min) {
403 1         2 $min = $g;
404             }
405 3 50       5 if ($b < $min) {
406 0         0 $min = $b;
407             }
408              
409 3         3 my ($h, $s, $l);
410 3 50       14 if ($max == $min) {
    100          
    100          
    50          
411 0         0 $h = 0;
412             } elsif ($maxc eq 'r') {
413 1         4 $h = 60 * (($g - $b) / ($max - $min)) % 360;
414             } elsif ($maxc eq 'g') {
415 1         2 $h = (60 * (($b - $r) / ($max - $min)) + 120);
416             } elsif ($maxc eq 'b') {
417 1         3 $h = (60 * (($r - $g) / ($max - $min)) + 240);
418             }
419              
420 3         4 $l = ($max + $min) / 2;
421              
422 3 50       31 if ($max == $min) {
    100          
423 0         0 $s = 0;
424             } elsif($l <= .5) {
425 2         4 $s = ($max - $min) / ($max + $min);
426             } else {
427 1         3 $s = ($max - $min) / (2 - ($max + $min));
428             }
429              
430 3         39 return sprintf("%.3g %.3g %.3g", $h, $s, $l);
431             }
432              
433             sub rgb2hsv {
434 3     3 1 2969 my ($rgb) = @_;
435              
436 3 50       50 my ($r, $g, $b) =
437             $rgb =~ $re_rgb or die "Invalid rgb color '$rgb', must be in 'ffffff' form";
438 3         8 for ($r, $g, $b) { $_ = hex($_)/255 }
  9         15  
439              
440 3         6 my $max = $r;
441 3         4 my $maxc = 'r';
442 3         4 my $min = $r;
443              
444 3 100       7 if ($g > $max) {
445 1         3 $max = $g;
446 1         2 $maxc = 'g';
447             }
448 3 100       6 if($b > $max) {
449 1         2 $max = $b;
450 1         2 $maxc = 'b';
451             }
452              
453 3 100       7 if($g < $min) {
454 1         2 $min = $g;
455             }
456 3 50       6 if($b < $min) {
457 0         0 $min = $b;
458             }
459              
460 3         4 my ($h, $s, $v);
461              
462 3 50       16 if ($max == $min) {
    100          
    100          
    50          
463 0         0 $h = 0;
464             } elsif ($maxc eq 'r') {
465 1         4 $h = 60 * (($g - $b) / ($max - $min)) % 360;
466             } elsif ($maxc eq 'g') {
467 1         4 $h = (60 * (($b - $r) / ($max - $min)) + 120);
468             } elsif ($maxc eq 'b') {
469 1         4 $h = (60 * (($r - $g) / ($max - $min)) + 240);
470             }
471              
472 3         4 $v = $max;
473 3 50       4 if($max == 0) {
474 0         0 $s = 0;
475             } else {
476 3         5 $s = 1 - ($min / $max);
477             }
478              
479 3         33 return sprintf("%.3g %.3g %.3g", $h, $s, $v);
480             }
481              
482             sub hsl2hsv {
483 9     9 1 2996 my $hsl = shift;
484              
485 9         30 my ($h, $s, $l) = split / /, $hsl;
486 9 100 66     56 $h>=0 && $h<=360 or $h = _wrap_h($h); $s>=0 && $s<=1 or die "Invalid S in HSL '$hsl', must be in 0-1"; $l>=0 && $l<=1 or die "Invalid L in HSL '$hsl', must be in 0-1";
  9 50 33     26  
  9 50 33     51  
487 9         14 my $_h = $h;
488 9         13 my $_s;
489             my $_v;
490              
491 9         13 $l *= 2;
492 9 100       17 $s *= ($l <= 1) ? $l : 2-$l;
493 9         16 $_v = ($l+$s) / 2;
494 9         14 $_s = (2*$s) / ($l+$s);
495              
496 9         73 "$_h $_s $_v";
497             }
498              
499             sub hsv2hsl {
500 5     5 1 3049 my $hsv = shift;
501              
502 5         18 my ($h, $s, $v) = split / /, $hsv;
503 5 100 66     32 $h>=0 && $h<=360 or $h = _wrap_h($h); $s>=0 && $s<=1 or die "Invalid S in HSV '$hsv', must be in 0-1"; $v>=0 && $v<=1 or die "Invalid V in HSV '$hsv', must be in 0-1";
  5 50 33     21  
  5 50 33     17  
504 5         9 my $_h = $h;
505 5         38 my $_s = $s * $v;
506 5         8 my $_l = (2-$s) * $v;
507              
508 5 50       15 $_s /= $_l <= 1 ? ($_l==0 ? 1 : $_l) : (2-$_l);
    100          
509 5         7 $_l /= 2;
510              
511 5         44 "$_h $_s $_l";
512             }
513              
514             sub hsl2rgb {
515 4     4 1 2979 hsv2rgb(hsl2hsv(shift));
516             }
517              
518             sub hsv2rgb {
519 8     8 1 2993 my $hsv = shift;
520              
521 8         22 my ($h, $s, $v) = split / /, $hsv;
522 8 100 66     36 $h>=0 && $h<=360 or $h = _wrap_h($h); $s>=0 && $s<=1 or die "Invalid S in HSV '$hsv', must be in 0-1"; $v>=0 && $v<=1 or die "Invalid V in HSV '$hsv', must be in 0-1";
  8 50 33     28  
  8 50 33     24  
523              
524 8         15 my $i = int($h/60);
525 8         12 my $f = $h/60 - $i;
526 8         12 my $p = $v * (1-$s);
527 8         12 my $q = $v * (1-$f*$s);
528 8         10 my $t = $v * (1-(1-$f)*$s);
529              
530 8         9 my ($r, $g, $b);
531 8 100       27 if ($i==0) {
    50          
    100          
    50          
    50          
532 4         5 $r = $v; $g = $t; $b = $p;
  4         6  
  4         5  
533             } elsif ($i==1) {
534 0         0 $r = $q; $g = $v; $b = $p;
  0         0  
  0         0  
535             } elsif ($i==2) {
536 2         4 $r = $p; $g = $v; $b = $t;
  2         3  
  2         4  
537             } elsif ($i==3) {
538 0         0 $r = $p; $g = $q; $b = $v;
  0         0  
  0         0  
539             } elsif ($i==4) {
540 2         3 $r = $t; $g = $p; $b = $v;
  2         4  
  2         3  
541             } else {
542 0         0 $r = $v; $g = $p; $b = $q;
  0         0  
  0         0  
543             }
544              
545 8         72 return sprintf("%02x%02x%02x", $r*255, $g*255, $b*255);
546             }
547              
548             my $basic_colors = {
549             black => "000000",
550             blue => "0000ff",
551             brown => "663333",
552             cyan => "00ffff", # a.k.a. aqua
553             green => "00ff00",
554             grey => "808080",
555             magenta => "ff00ff", # a.k.a. fuchsia
556             orange => "ff8000",
557             pink => "ffcccc",
558             purple => "800080", # a.k.a. violet
559             red => "ff0000",
560             white => "ffffff",
561             yellow => "ffff00",
562             };
563             sub rgb_closest_to {
564 0 0   0 1   my $opts = ref($_[0]) eq 'HASH' ? shift : {};
565 0   0       my $colors = $opts->{colors} // $basic_colors;
566 0           my $rgb = shift;
567              
568 0           my $min_diff; my $closest_color;
569 0           for my $colorname (sort keys %$colors) {
570 0           my $diff = rgb_diff($rgb, $colors->{$colorname}, 'hsv_hue1');
571 0 0 0       if (!defined($min_diff) || $min_diff > $diff) {
572 0           $closest_color = $colorname;
573 0           $min_diff = $diff;
574             }
575             }
576 0           $closest_color;
577             }
578              
579             1;
580             # ABSTRACT: Utilities related to RGB colors
581              
582             __END__