]> git.ktnx.net Git - mobile-ledger.git/blob - tools/gen-styles
darker colour palette, matching theme (pre-generated) and run-time colours
[mobile-ledger.git] / tools / gen-styles
1 #!/usr/bin/perl
2
3 use strict; use warnings; use utf8;
4 use autodie;
5 use Math::Trig;
6 use File::Basename qw(basename dirname);
7 use File::Temp qw(tempfile);
8
9 sub hexTuple {
10         my ($r, $g, $b) = @_;
11         return sprintf('%02x%02x%02x', int(255*$r+0.5), int(255*$g+0.5), int(255*$b+0.5));
12 }
13 sub hsvHex {
14         my ($hue, $sat, $val ) = @_;
15         my $h = int($hue * 6);
16         my $f = $hue * 6 - $h;
17         my $p = $val * (1 - $sat);
18         my $q = $val * ( 1 - $f * $sat);
19         my $t = $val * ( 1 - (1-$f) * $sat);
20
21         return hexTuple($val, $t, $p) if $h == 0 or $h == 6;
22         return hexTuple($q, $val, $p) if $h == 1;
23         return hexTuple($p, $val, $t) if $h == 2;
24         return hexTuple($p, $q, $val) if $h == 3;
25         return hexTuple($t, $p, $val) if $h == 4;
26         return hexTuple($val, $p, $q) if $h == 5;
27
28         die $h;
29 }
30
31 # https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
32 sub hslHex {
33         my ($hue, $sat, $lig ) = @_;
34         $hue = $hue / 360.0;
35         my $h = ($hue * 6.0);
36         my $c = (1 - abs(2.0*$lig - 1)) * $sat;
37         my $h_mod_2 = $h - 2.0*int($h/2);
38         my $x = $c * (1 - abs($h_mod_2 - 1));
39         my ($r, $g, $b);
40         my $m = $lig - $c / 2.0;
41
42         return hexTuple($c + $m, $x + $m,  0 + $m) if $h < 1 or $h == 6;
43         return hexTuple($x + $m, $c + $m,  0 + $m) if $h < 2;
44         return hexTuple( 0 + $m, $c + $m, $x + $m) if $h < 3;
45         return hexTuple( 0 + $m, $x + $m, $c + $m) if $h < 4;
46         return hexTuple($x + $m,  0 + $m, $c + $m) if $h < 5;
47         return hexTuple($c + $m,  0 + $m, $x + $m) if $h < 6;
48
49         die $h;
50 }
51
52 my @hexDigit = split //, '0123456789abcdef';
53 my %hexValue = map(
54         (lc($hexDigit[$_]) => $_, uc($hexDigit[$_]) => $_ ),
55         0..15 );
56
57 sub min {
58         my $min = shift;
59
60         for (@_) { $min = $_ if $_ < $min }
61
62         return $min;
63 }
64
65 sub max {
66         my $max = shift;
67
68         for (@_) { $max = $_ if $_ > $max }
69
70         return $max;
71 }
72
73 sub hexToRGB {
74         my $hexTriplet = shift;
75
76         my @d = $hexTriplet =~ /^#?(.)(.)(.)(.)(.)(.)/;
77
78         return (16 * $hexValue{$d[0]} + $hexValue{$d[1]},
79                 16 * $hexValue{$d[2]} + $hexValue{$d[3]},
80                 16 * $hexValue{$d[4]} + $hexValue{$d[5]});
81 }
82
83 sub hexToHSL {
84         my $hexTriplet = shift;
85
86         my ($r,$g,$b) = hexToRGB($hexTriplet);
87         warn "$hexTriplet -> $r:$g:$b";
88
89         for ($r, $g, $b ) { $_ = $_ / 255.0 }
90
91         my $M = max($r, $g, $b);
92         my $m = min($r, $g, $b);
93         my $C = $M - $m;
94
95         my $h;
96         if ($C == 0) {
97                 $h = 0;
98         }
99         elsif ( $r == $M ) {
100                 $h = ($g-$b)/$C;
101                 $h -= 6*int($h/6.0);
102         }
103         elsif ( $g == $M ) {
104                 $h = ($b-$r)/$C + 2;
105         }
106         elsif ( $b == $M ) {
107                 $h = ($r-$g)/$C + 4;
108         }
109         else { die "$C, $M, $r, $g, $b"; }
110
111         my $H = 60 * $h;
112         my $L = ($M + $m) / 2;
113
114         my $S = ( $L <= 0.5 ) ? $C/(2*$L) : $C / (2-2*$L);
115
116         return( $H, $S, $L );
117 }
118
119 my $baseColorHSV = [ hexToHSL('#935ff2') ];
120 my $baseColorHue = $baseColorHSV->[0];
121 warn sprintf( 'H:%1.4f S:%1.4f V:%1.4f', @$baseColorHSV );
122 warn sprintf( 'H:%1.4f S:%1.4f L:%1.4f', hexToHSL('#3e148c') );
123 my @target = hexToRGB('#935ff2');
124 my ($best, $min_dist);
125 for (my $s = 0.50; $s < 0.90; $s += 0.001) {
126         for ( my $l = 0.50; $l <= 0.80; $l += 0.001 ) {
127                 my $hexColor = hslHex($baseColorHue, $s, $l);
128                 my ($r,$g,$b) = hexToRGB( $hexColor );
129                 my $dist = abs($r-$target[0])
130                          + abs($g-$target[1])
131                          + abs($b-$target[2]);
132                 if (not defined($best) or $dist < $min_dist) {
133                         $best = [ $s, $l, $hexColor ];
134                         $min_dist = $dist;
135                 }
136         }
137 }
138 warn sprintf( 's%1.3f, l%1.3f -> %s',
139         @$best );
140
141 my $baseTheme = "AppTheme.NoActionBar";
142
143 use constant STEP_DEGREES => 5;
144
145 # # hsb
146 # for( my $hue = 0; $hue < 360; $hue += STEP_DEGREES ) {
147 #       printf "<style name=\"%s.%03d\" parent=\"%s\">\n",
148 #               $baseTheme, $hue, $baseTheme;
149 #       printf "  <item name=\"colorPrimary\">#%s</item>\n",
150 #                       hsvHex($hue/360.0, 0.61, 0.95);
151 #       printf "  <item name=\"colorPrimaryDark\">#%s</item>\n",
152 #                       hsvHex($hue/360.0, 0.86, 0.55);
153 #       printf "  <item name=\"colorAccent\">#%s</item>\n",
154 #                       hsvHex(($hue-4)/360.0, 0.72, 0.82);
155 #       printf "  <item name=\"drawer_background\">#ffffffff</item>\n";
156 #       printf "  <item name=\"table_row_dark_bg\">#28%s</item>\n",
157 #                       hsvHex($hue/360.0, 0.65, 0.83);
158 #       printf "  <item name=\"table_row_light_bg\">#28%s</item>\n",
159 #                       hsvHex($hue/360.0, 0.20, 1.00);
160 #       printf "  <item name=\"header_border\">#80%s</item>\n",
161 #                       hsvHex(($hue+6)/360.0, 0.86, 0.55);
162 #       printf "</style>\n";
163 # }
164
165 # HSL
166 sub outputThemes {
167         my $out = shift;
168         my $baseIndent = shift;
169         $out->print(hslStyleForHue($baseColorHue, undef, $baseIndent));
170         for( my $hue = 0; $hue < 360; $hue += STEP_DEGREES ) {
171                 $out->print("\n");
172                 $out->print(hslStyleForHue($hue, $baseTheme, $baseIndent));
173         }
174 }
175
176 sub hslStyleForHue {
177         my $hue = shift;
178         my $base = shift;
179         my $baseIndent = shift // '';
180
181         my %lQ = (
182                 0   => 0.450,   # red
183                 60  => 0.400,   # yellow
184                 120 => 0.400,   # green
185                 180 => 0.400,   # cyan
186                 240 => 0.750,   # blue
187                 300 => 0.500,   # magenta
188         );
189         $lQ{360} = $lQ{0};
190
191         my ($x0, $x1, $y0, $y1);
192         $x0 = (int( $hue / 60 ) * 60) % 360;
193         $x1 = $x0 + 60;
194         $y0 = $lQ{$x0};
195         $y1 = $lQ{$x1};
196
197         # linear interpolation
198         my $l1 = $y0 + 1.0 * ( $hue - $x0 ) * ( $y1 - $y0 ) / ( $x1 - $x0 );
199
200         my $l2 = $l1 * 0.80;
201         my $l3 = 0.950;
202         my $l4 = 0.980;
203
204         my $result = "";
205         my $indent = "$baseIndent    ";
206
207         if ($base) {
208                 $result .= sprintf "$baseIndent<style name=\"%s.%03d\" parent=\"%s\">\n",
209                         $baseTheme, $hue, $baseTheme;
210         }
211         else {
212                 $result .= sprintf "$baseIndent<style name=\"%s\">\n",
213                         $baseTheme;
214                 $result .= "$indent<item name=\"windowActionBar\">false</item>\n";
215                 $result .= "$indent<item name=\"windowNoTitle\">true</item>\n";
216                 $result .= "$indent<item name=\"textColor\">#757575</item>\n";
217         }
218         my $S = 0.845;
219         $result .= sprintf "$indent<item name=\"colorPrimary\">#%s</item>\n",
220                 hslHex($hue, $S, $l1);
221         $result .= sprintf "$indent<item name=\"colorPrimaryTransparent\">#00%s</item>\n",
222                 hslHex($hue, $S, $l1);
223         $result .= sprintf "$indent<item name=\"colorAccent\">#%s</item>\n",
224                 hslHex($hue, $S, $l2);
225         $result .= "$indent<item name=\"drawer_background\">#ffffffff</item>\n";
226         $result .= sprintf "$indent<item name=\"table_row_dark_bg\">#%s</item>\n",
227                 hslHex($hue, $S, $l3);
228         $result .= sprintf "$indent<item name=\"table_row_light_bg\">#%s</item>\n",
229                 hslHex($hue, $S, $l4);
230         $result .= "$baseIndent</style>\n";
231
232         return $result;
233 }
234
235 my $xml = shift;
236
237 if ($xml) {
238         my $start_marker = '<!-- theme list start -->';
239         my $end_marker = '<!-- theme list end -->';
240         my ($fh, $filename) = tempfile(basename($0).'.XXXXXXXX', DIR => dirname($xml));
241         open(my $in, '<', $xml);
242         my $base_indent = '';
243         my $state = 'waiting-for-start-marker';
244         while (<$in>) {
245                 if ( $state eq 'waiting-for-start-marker' ) {
246                         print $fh $_;
247                         $state = 'skipping-styles', $base_indent = $1
248                                 if /^(\s*)\Q$start_marker\E/;
249                         next;
250                 }
251                 if ( $state eq 'skipping-styles' ) {
252                         next unless /^\s*\Q$end_marker\E/;
253                         outputThemes($fh, $base_indent);
254                         print $fh $_;
255                         $state = 'copying-the-rest';
256                         next;
257                 }
258                 if ( $state eq 'copying-the-rest') {
259                         print $fh $_;
260                         next;
261                 }
262
263                 die "Unexpected state '$state'";
264         }
265
266         close($fh);
267         close($in);
268
269         rename($filename, $xml);
270 }
271 else {
272         outputThemes(\*STDOUT);
273 }