X-Git-Url: https://git.ktnx.net/?a=blobdiff_plain;ds=sidebyside;f=tools%2Fgen-styles;h=25002e743e14d5d71f16e6ea6381453c0e9bdbfb;hb=e6fdbea0f85cdab59640f41e6365875d26880810;hp=0b8759c2942e9b5b9bdf68dc700952e3918c011a;hpb=fbaaba448b36e6f43d357c7651acef6746670f6e;p=mobile-ledger.git
diff --git a/tools/gen-styles b/tools/gen-styles
index 0b8759c2..25002e74 100644
--- a/tools/gen-styles
+++ b/tools/gen-styles
@@ -1,188 +1,292 @@
#!/usr/bin/perl
-use strict; use warnings; use utf8;
+use strict; use warnings; use utf8::all;
use autodie;
-use Math::Trig;
-use File::Basename qw(basename dirname);
-use File::Temp qw(tempfile);
+use Carp;
+
+use Type::Tiny;
+use Types::Standard qw(StrictNum);
+my $colorValue = Type::Tiny->new(
+ parent => StrictNum,
+ constraint => '($_ >= 0) and ($_ <= 1)'
+);
+
+package Color::HSL;
+use Moo;
+use namespace::clean;
+
+has h => ( is => 'ro', isa => $colorValue );
+has s => ( is => 'ro', isa => $colorValue );
+has l => ( is => 'ro', isa => $colorValue );
+
+package Color::sRGB;
+use Moo;
+use Types::Standard qw(StrictNum);
+use List::MoreUtils qw(minmax);
+
+use namespace::clean;
+
+use overload '""' => 'hexTuple';
+
+has r => ( is => 'ro', isa => $colorValue );
+has g => ( is => 'ro', isa => $colorValue );
+has b => ( is => 'ro', isa => $colorValue );
sub hexTuple {
- my ($r, $g, $b) = @_;
- return sprintf('%02x%02x%02x', int(255*$r+0.5), int(255*$g+0.5), int(255*$b+0.5));
+ my $self = shift;
+ return sprintf( '%02x%02x%02x',
+ int( $self->r * 255 + 0.5 ),
+ int( $self->g * 255 + 0.5 ),
+ int( $self->b * 255 + 0.5 ) );
}
-sub hsvHex {
- my ($hue, $sat, $val ) = @_;
- my $h = int($hue * 6);
- my $f = $hue * 6 - $h;
- my $p = $val * (1 - $sat);
- my $q = $val * ( 1 - $f * $sat);
- my $t = $val * ( 1 - (1-$f) * $sat);
- return hexTuple($val, $t, $p) if $h == 0 or $h == 6;
- return hexTuple($q, $val, $p) if $h == 1;
- return hexTuple($p, $val, $t) if $h == 2;
- return hexTuple($p, $q, $val) if $h == 3;
- return hexTuple($t, $p, $val) if $h == 4;
- return hexTuple($val, $p, $q) if $h == 5;
+# https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
+sub _norm {
+ return $_[0] / 12.92 if $_[0] <= 0.03928;
+ return ( ( $_[0] + 0.055 ) / 1.055 )**2.4;
+}
- die $h;
+sub relativeLuminance {
+ my $self = shift;
+
+ return 0.2126 * _norm( $self->r ) + 0.7152 * _norm( $self->g )
+ + 0.0722 * _norm( $self->b );
}
-# https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
-sub hslHex {
- my ($hue, $sat, $lig ) = @_;
- $hue = $hue / 360.0;
- my $h = ($hue * 6.0);
- my $c = (1 - abs(2.0*$lig - 1)) * $sat;
- my $h_mod_2 = $h - 2.0*int($h/2);
- my $x = $c * (1 - abs($h_mod_2 - 1));
- my ($r, $g, $b);
- my $m = $lig - $c / 2.0;
-
- return hexTuple($c + $m, $x + $m, 0 + $m) if $h < 1 or $h == 6;
- return hexTuple($x + $m, $c + $m, 0 + $m) if $h < 2;
- return hexTuple( 0 + $m, $c + $m, $x + $m) if $h < 3;
- return hexTuple( 0 + $m, $x + $m, $c + $m) if $h < 4;
- return hexTuple($x + $m, 0 + $m, $c + $m) if $h < 5;
- return hexTuple($c + $m, 0 + $m, $x + $m) if $h < 6;
+sub BLACK {
+ shift->new( r => 0, g => 0, b => 0 );
+}
- die $h;
+sub WHITE {
+ shift->new( r => 1, g => 1, b => 1 );
}
my @hexDigit = split //, '0123456789abcdef';
-my %hexValue = map(
- (lc($hexDigit[$_]) => $_, uc($hexDigit[$_]) => $_ ),
- 0..15 );
+my %hexValue =
+ map( ( lc( $hexDigit[$_] ) => $_, uc( $hexDigit[$_] ) => $_ ), 0 .. 15 );
-sub min {
- my $min = shift;
+sub fromHexTriplet {
+ my ( $class, $triplet ) = @_;
- for (@_) { $min = $_ if $_ < $min }
+ my @d = $triplet =~ /^#?(.)(.)(.)(.)(.)(.)$/
+ or die "'$triplet' is not a valid colour triplet";
- return $min;
+ return $class->new(
+ r => ( 16 * $hexValue{ $d[0] } + $hexValue{ $d[1] } ) / 255.0,
+ g => ( 16 * $hexValue{ $d[2] } + $hexValue{ $d[3] } ) / 255.0,
+ b => ( 16 * $hexValue{ $d[4] } + $hexValue{ $d[5] } ) / 255.0
+ );
+}
+
+# https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
+sub fromHSL {
+ my ( $class, $hsl ) = @_;
+ my $hue = $hsl->h;
+ my $sat = $hsl->s;
+ my $lig = $hsl->l;
+
+ my $h = ( $hue * 6.0 );
+ my $c = ( 1 - abs( 2.0 * $lig - 1 ) ) * $sat;
+ my $h_mod_2 = $h - 2.0 * int( $h / 2 );
+ my $x = $c * ( 1 - abs( $h_mod_2 - 1 ) );
+ my ( $r, $g, $b );
+ my $m = $lig - $c / 2.0;
+
+ return $class->new( r => $c + $m, g => $x + $m, b => 0 + $m )
+ if $h < 1 or $h == 6;
+ return $class->new( r => $x + $m, g => $c + $m, b => 0 + $m ) if $h < 2;
+ return $class->new( r => 0 + $m, g => $c + $m, b => $x + $m ) if $h < 3;
+ return $class->new( r => 0 + $m, g => $x + $m, b => $c + $m ) if $h < 4;
+ return $class->new( r => $x + $m, g => 0 + $m, b => $c + $m ) if $h < 5;
+ return $class->new( r => $c + $m, g => 0 + $m, b => $x + $m ) if $h < 6;
+
+ die $h;
}
-sub max {
- my $max = shift;
+sub toHSL {
+ my $self = shift;
- for (@_) { $max = $_ if $_ > $max }
+ my ( $m, $M ) = minmax( $self->r, $self->g, $self->b );
- return $max;
+ my $C = $M - $m;
+
+ my $h;
+ if ( $C == 0 ) {
+ $h = 0;
+ }
+ elsif ( $self->r == $M ) {
+ $h = ( $self->g - $self->b ) / $C;
+ $h -= 6 * int( $h / 6.0 );
+ }
+ elsif ( $self->g == $M ) {
+ $h = ( $self->b - $self->r ) / $C + 2;
+ }
+ elsif ( $self->b == $M ) {
+ $h = ( $self->r - $self->g ) / $C + 4;
+ }
+ else { die "$C, $M, $self"; }
+
+ my $H = 60 * $h;
+ my $L = ( $M + $m ) / 2;
+
+ my $S = ( $L <= 0.5 ) ? $C / ( 2 * $L ) : $C / ( 2 - 2 * $L );
+
+ return Color::HSL->new( h => $H/360.0, s => $S, l => $L );
}
-sub hexToRGB {
- my $hexTriplet = shift;
+sub contrastWith {
+ my ( $self, $ref ) = @_;
- my @d = $hexTriplet =~ /^#?(.)(.)(.)(.)(.)(.)/;
+ my $myL = $self->relativeLuminance;
+ my $refL = $ref->relativeLuminance;
- return (16 * $hexValue{$d[0]} + $hexValue{$d[1]},
- 16 * $hexValue{$d[2]} + $hexValue{$d[3]},
- 16 * $hexValue{$d[4]} + $hexValue{$d[5]});
+ my $ratio = ( $myL + 0.05 ) / ( $refL + 0.05 );
+ $ratio = 1 / $ratio if $ratio < 1;
+ return $ratio;
}
-sub hexToHSL {
- my $hexTriplet = shift;
+package MAIN;
- my ($r,$g,$b) = hexToRGB($hexTriplet);
- warn "$hexTriplet -> $r:$g:$b";
+use Math::Trig;
+use File::Basename qw(basename dirname);
+use File::Temp qw(tempfile);
+use Getopt::Long;
- for ($r, $g, $b ) { $_ = $_ / 255.0 }
+my $opt_night;
- my $M = max($r, $g, $b);
- my $m = min($r, $g, $b);
- my $C = $M - $m;
+GetOptions(
+ 'night!' => \$opt_night,
+) or exit 1;
- my $h;
- if ($C == 0) {
- $h = 0;
- }
- elsif ( $r == $M ) {
- $h = ($g-$b)/$C;
- $h -= 6*int($h/6.0);
- }
- elsif ( $g == $M ) {
- $h = ($b-$r)/$C + 2;
- }
- elsif ( $b == $M ) {
- $h = ($r-$g)/$C + 4;
- }
- else { die "$C, $M, $r, $g, $b"; }
+my $DEFAULT_HUE = 261.2245;
- my $H = 60 * $h;
- my $L = ($M + $m) / 2;
+sub hexTuple {
+ my ($r, $g, $b) = @_;
+ return sprintf('%02x%02x%02x', int(255*$r+0.5), int(255*$g+0.5), int(255*$b+0.5));
+}
+sub hsvHex {
+ my ($hue, $sat, $val ) = @_;
+ my $h = int($hue * 6);
+ my $f = $hue * 6 - $h;
+ my $p = $val * (1 - $sat);
+ my $q = $val * ( 1 - $f * $sat);
+ my $t = $val * ( 1 - (1-$f) * $sat);
- my $S = ( $L <= 0.5 ) ? $C/(2*$L) : $C / (2-2*$L);
+ return hexTuple($val, $t, $p) if $h == 0 or $h == 6;
+ return hexTuple($q, $val, $p) if $h == 1;
+ return hexTuple($p, $val, $t) if $h == 2;
+ return hexTuple($p, $q, $val) if $h == 3;
+ return hexTuple($t, $p, $val) if $h == 4;
+ return hexTuple($val, $p, $q) if $h == 5;
- return( $H, $S, $L );
+ die $h;
}
-my $baseColorHSV = [ hexToHSL('#935ff2') ];
-my $baseColorHue = $baseColorHSV->[0];
-warn sprintf( 'H:%1.4f S:%1.4f V:%1.4f', @$baseColorHSV );
-warn sprintf( 'H:%1.4f S:%1.4f L:%1.4f', hexToHSL('#3e148c') );
-my @target = hexToRGB('#935ff2');
-my ($best, $min_dist);
-for (my $s = 0.50; $s < 0.90; $s += 0.001) {
- for ( my $l = 0.50; $l <= 0.80; $l += 0.001 ) {
- my $hexColor = hslHex($baseColorHue, $s, $l);
- my ($r,$g,$b) = hexToRGB( $hexColor );
- my $dist = abs($r-$target[0])
- + abs($g-$target[1])
- + abs($b-$target[2]);
- if (not defined($best) or $dist < $min_dist) {
- $best = [ $s, $l, $hexColor ];
- $min_dist = $dist;
- }
- }
+sub hslHex {
+ my ( $h, $s, $l ) = @_;
+ return Color::sRGB->fromHSL(
+ Color::HSL->new( { h => $h / 360.0, s => $s, l => $l } ) )->hexTuple;
}
-warn sprintf( 's%1.3f, l%1.3f -> %s',
- @$best );
+
+warn sprintf("%s: %2.1f\n", 'white', Color::sRGB->WHITE->relativeLuminance);
+warn sprintf("%s: %2.1f\n", 'black', Color::sRGB->BLACK->relativeLuminance);
+warn sprintf( "%s: %2.1f\n",
+ '50% gray',
+ Color::sRGB->new( r => 0.5, g => 0.5, b => 0.5 )->relativeLuminance );
+
+my $baseColor = '#935ff2';
+my $baseColorRGB = Color::sRGB->fromHexTriplet($baseColor);
+my $baseColorHSL = $baseColorRGB->toHSL;
+my $baseColorHue = $baseColorHSL->h;
+warn sprintf(
+ '%s â H:%1.4f S:%1.4f L:%1.4f (luminance: %1.4f; cW: %1.4f, cB: %1.4f)',
+ $baseColor,
+ 360 * $baseColorHSL->h,
+ $baseColorHSL->s,
+ $baseColorHSL->l,
+ $baseColorRGB->relativeLuminance,
+ $baseColorRGB->contrastWith( Color::sRGB->WHITE ),
+ $baseColorRGB->contrastWith( Color::sRGB->BLACK ),
+);
+# # find best saturation/lightness for the desired color
+# # test if the above is correct
+# my ($best, $min_dist);
+# for (my $s = 0.50; $s < 0.90; $s += 0.001) {
+# for ( my $l = 0.50; $l <= 0.80; $l += 0.001 ) {
+# my $color = Color::sRGB->fromHSL(
+# Color::HSL->new( h => $baseColorHue, s => $s, l => $l ) );
+# my $dist =
+# abs( $color->r - $baseColorRGB->r )
+# + abs( $color->g - $baseColorRGB->g )
+# + abs( $color->b - $baseColorRGB->b );
+# if ( not defined($best) or $dist < $min_dist ) {
+# $best = [ $s, $l, $color ];
+# $min_dist = $dist;
+# }
+# }
+# }
+# warn sprintf( 's%1.3f, l%1.3f â %s', @$best );
my $baseTheme = "AppTheme";
use constant STEP_DEGREES => 5;
-# # hsb
-# for( my $hue = 0; $hue < 360; $hue += STEP_DEGREES ) {
-# printf "\n";
-# }
-
-# HSL
sub outputThemes {
- my $out = shift;
- my $baseIndent = shift;
- for( my $hue = 0; $hue < 360; $hue += STEP_DEGREES ) {
- $out->print("\n");
- $out->print(hslStyleForHue($hue, $baseTheme, $baseIndent));
- }
+ my $out = shift;
+ my $baseIndent = shift;
+ $out->print("\n");
+ $out->print(
+ hslStyleForHue( $DEFAULT_HUE, $baseTheme, $baseIndent, 'default' ) );
+ for ( my $hue = 0; $hue < 360; $hue += STEP_DEGREES ) {
+ $out->print("\n");
+ $out->print( hslStyleForHue( $hue, $baseTheme, $baseIndent ) );
+ }
+}
+
+sub bestLightnessForHue {
+ my ( $h, $s ) = @_;
+ my $targetContrast = 4.07;
+ my $white = Color::sRGB->WHITE;
+ my $bestLightness;
+ my $bestContrast;
+ for ( my $l = 0; $l < 1; $l += 0.002 ) {
+ my $contrast = Color::sRGB->fromHSL(
+ Color::HSL->new( { h => $h, s => $s, l => $l } ) )
+ ->contrastWith($white);
+
+ if ( defined $bestLightness ) {
+ if (abs( $contrast - $targetContrast ) <
+ abs( $bestContrast - $targetContrast ) )
+ {
+ $bestLightness = $l;
+ $bestContrast = $contrast;
+ }
+ }
+ else {
+ $bestLightness = $l;
+ $bestContrast = $contrast;
+ }
+ }
+
+ warn sprintf(
+ "Found best lightness for hue %1.4f: %1.4f (contrast %1.4f)\n",
+ 360 * $h, $bestLightness, $bestContrast );
+ return $bestLightness;
}
sub hslStyleForHue {
my $hue = shift;
my $base = shift;
my $baseIndent = shift // '';
+ my $subTheme = shift // sprintf('%03d', $hue);
my %lQ = (
- 0 => 0.450, # red
- 60 => 0.400, # yellow
- 120 => 0.400, # green
- 180 => 0.400, # cyan
- 240 => 0.750, # blue
- 300 => 0.500, # magenta
+ 0 => 0.550, # red
+ 60 => 0.250, # yellow
+ 120 => 0.290, # green
+ 180 => 0.300, # cyan
+ 240 => 0.680, # blue
+ 300 => 0.450, # magenta
);
$lQ{360} = $lQ{0};
@@ -192,38 +296,45 @@ sub hslStyleForHue {
$y0 = $lQ{$x0};
$y1 = $lQ{$x1};
+ my $S = 0.8497;
+
# linear interpolation
- my $l1 = $y0 + 1.0 * ( $hue - $x0 ) * ( $y1 - $y0 ) / ( $x1 - $x0 );
+ #my $l1 = $y0 + 1.0 * ( $hue - $x0 ) * ( $y1 - $y0 ) / ( $x1 - $x0 );
+ my $l1 = bestLightnessForHue( $hue / 360.0, $S );
my $l2 = $l1 * 0.80;
- my $l3 = 0.950;
- my $l4 = 0.980;
+ my $l3 = $opt_night ? 0.150 : 0.950;
+ my $l4 = $opt_night ? 0.100 : 0.980;
my $result = "";
my $indent = "$baseIndent ";
if ($base) {
- $result .= sprintf "$baseIndent\n";
return $result;
@@ -235,6 +346,7 @@ if ($xml) {
my $start_marker = '';
my $end_marker = '';
my ($fh, $filename) = tempfile(basename($0).'.XXXXXXXX', DIR => dirname($xml));
+ $fh->binmode(':utf8');
open(my $in, '<', $xml);
my $base_indent = '';
my $state = 'waiting-for-start-marker';