File Coverage

blib/lib/Term/ANSIColor/Gradients/Utils.pm
Criterion Covered Total %
statement 69 81 85.1
branch 22 40 55.0
condition n/a
subroutine 8 8 100.0
pod 2 5 40.0
total 101 134 75.3


line stmt bran cond sub pod time code
1             package Term::ANSIColor::Gradients::Utils ;
2              
3 1     1   880 use strict ;
  1         2  
  1         40  
4 1     1   6 use warnings ;
  1         1  
  1         49  
5 1     1   5 use Exporter 'import' ;
  1         1  
  1         1457  
6              
7             our $VERSION = '0.10' ;
8             our @EXPORT_OK = qw(build_contrast intensity_shift) ;
9              
10             # ---------------------------------------------------------------------------
11             # ANSI 256 color index to RGB table, built once at load time
12             # ---------------------------------------------------------------------------
13              
14             my @CUBE = (0, 95, 135, 175, 215, 255) ;
15              
16             my @SYS_RGB =
17             (
18             [ 0, 0, 0], [128, 0, 0], [ 0,128, 0], [128,128, 0],
19             [ 0, 0,128], [128, 0,128], [ 0,128,128], [192,192,192],
20             [128,128,128], [255, 0, 0], [ 0,255, 0], [255,255, 0],
21             [ 0, 0,255], [255, 0,255], [ 0,255,255], [255,255,255],
22             ) ;
23              
24             my @ANSI_RGB ;
25              
26             {
27             $ANSI_RGB[$_] = $SYS_RGB[$_] for 0 .. 15 ;
28              
29             for my $i (16 .. 231)
30             {
31             my $n = $i - 16 ;
32             my $r = int($n / 36) ;
33             my $g = int(($n % 36) / 6) ;
34             my $b = $n % 6 ;
35             $ANSI_RGB[$i] = [$CUBE[$r], $CUBE[$g], $CUBE[$b]] ;
36             }
37              
38             for my $i (232 .. 255)
39             {
40             my $v = 8 + ($i - 232) * 10 ;
41             $ANSI_RGB[$i] = [$v, $v, $v] ;
42             }
43             }
44              
45             # ---------------------------------------------------------------------------
46              
47             sub rgb_to_hsv
48             {
49 3     3 0 7 my ($r, $g, $b) = @_ ;
50              
51 3         8 $r /= 255.0 ;
52 3         7 $g /= 255.0 ;
53 3         4 $b /= 255.0 ;
54              
55 3         6 my $max = $r ;
56 3 50       10 $max = $g if $g > $max ;
57 3 50       9 $max = $b if $b > $max ;
58              
59 3         5 my $min = $r ;
60 3 50       7 $min = $g if $g < $min ;
61 3 50       7 $min = $b if $b < $min ;
62              
63 3         6 my $delta = $max - $min ;
64 3         5 my $v = $max ;
65 3 50       11 my $s = $max > 0 ? $delta / $max : 0 ;
66 3         4 my $h = 0 ;
67              
68 3 50       8 if ($delta > 0)
69             {
70 3 50       44 if ($max == $r) { $h = 60 * (($g - $b) / $delta) }
  0 50       0  
71 0         0 elsif ($max == $g) { $h = 60 * (($b - $r) / $delta) + 120 }
72 3         9 else { $h = 60 * (($r - $g) / $delta) + 240 }
73              
74 3 50       10 $h += 360 if $h < 0 ;
75             }
76              
77 3         10 return ($h, $s, $v) ;
78             }
79              
80             # ---------------------------------------------------------------------------
81              
82             sub hsv_to_rgb
83             {
84 3     3 0 7 my ($h, $s, $v) = @_ ;
85              
86 3 50       8 if ($s == 0)
87             {
88 0         0 my $c = int($v * 255 + 0.5) ;
89 0         0 return ($c, $c, $c) ;
90             }
91              
92 3         5 $h /= 60.0 ;
93 3         6 my $i = int($h) ;
94 3         6 my $f = $h - $i ;
95 3         6 my $p = $v * (1 - $s) ;
96 3         6 my $q = $v * (1 - $s * $f) ;
97 3         5 my $t = $v * (1 - $s * (1 - $f)) ;
98              
99 3         7 my ($r, $g, $b) ;
100 3 50       14 if ($i == 0) { ($r, $g, $b) = ($v, $t, $p) }
  0 100       0  
    50          
    50          
    50          
101 1         3 elsif ($i == 1) { ($r, $g, $b) = ($q, $v, $p) }
102 0         0 elsif ($i == 2) { ($r, $g, $b) = ($p, $v, $t) }
103 0         0 elsif ($i == 3) { ($r, $g, $b) = ($p, $q, $v) }
104 2         5 elsif ($i == 4) { ($r, $g, $b) = ($t, $p, $v) }
105 0         0 else { ($r, $g, $b) = ($v, $p, $q) }
106              
107 3         15 return (int($r * 255 + 0.5), int($g * 255 + 0.5), int($b * 255 + 0.5)) ;
108             }
109              
110             # ---------------------------------------------------------------------------
111              
112             sub nearest_ansi
113             {
114 3     3 0 6 my ($tr, $tg, $tb) = @_ ;
115              
116 3         5 my $best_idx = 0 ;
117 3         5 my $best_dist = 1e18 ;
118              
119 3         9 for my $i (0 .. 255)
120             {
121 768         1004 my ($r, $g, $b) = @{$ANSI_RGB[$i]} ;
  768         1400  
122 768         1361 my $d = ($r - $tr) ** 2 + ($g - $tg) ** 2 + ($b - $tb) ** 2 ;
123              
124 768 100       1525 if ($d < $best_dist)
125             {
126 10         14 $best_dist = $d ;
127 10         15 $best_idx = $i ;
128             }
129             }
130              
131 3         23 return $best_idx ;
132             }
133              
134             # ---------------------------------------------------------------------------
135              
136             sub build_contrast
137             {
138 1     1 1 570 my ($idx) = @_ ;
139              
140 1         3 my ($r, $g, $b) = @{$ANSI_RGB[$idx]} ;
  1         5  
141 1         4 my ($h, $s, $v) = rgb_to_hsv($r, $g, $b) ;
142              
143             # Greyscale: no meaningful hue, flip luminance instead
144 1 50       5 if ($s < 0.15)
145             {
146 0         0 my $lum = 0.299 * $r + 0.587 * $g + 0.114 * $b ;
147 0 0       0 my $cv = $lum < 128 ? 1.0 : 0.0 ;
148 0         0 my ($nr, $ng, $nb) = hsv_to_rgb($h, $s, $cv) ;
149              
150 0         0 return nearest_ansi($nr, $ng, $nb) ;
151             }
152              
153             # Chromatic: complementary hue, same saturation and value
154 1         2 my $ch = ($h + 180) % 360 ;
155 1         7 my ($nr, $ng, $nb) = hsv_to_rgb($ch, $s, $v) ;
156              
157 1         5 return nearest_ansi($nr, $ng, $nb) ;
158             }
159              
160             # ---------------------------------------------------------------------------
161              
162             sub intensity_shift
163             {
164 2     2 1 661 my ($idx, $delta) = @_ ;
165              
166 2         4 my ($r, $g, $b) = @{$ANSI_RGB[$idx]} ;
  2         7  
167 2         6 my ($h, $s, $v) = rgb_to_hsv($r, $g, $b) ;
168              
169 2         6 $v += $delta * 0.05 ;
170 2 50       6 $v = 0 if $v < 0 ;
171 2 100       7 $v = 1 if $v > 1 ;
172              
173 2         5 my ($nr, $ng, $nb) = hsv_to_rgb($h, $s, $v) ;
174              
175 2         6 return nearest_ansi($nr, $ng, $nb) ;
176             }
177              
178             1 ;
179              
180             __END__