+use strict; use warnings; use utf8::all;
+use autodie;
+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 $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 ) );
+}
+
+# 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;
+}
+
+sub relativeLuminance {
+ my $self = shift;
+
+ return 0.2126 * _norm( $self->r ) + 0.7152 * _norm( $self->g )
+ + 0.0722 * _norm( $self->b );
+}
+
+sub BLACK {
+ shift->new( r => 0, g => 0, b => 0 );
+}
+
+sub WHITE {
+ shift->new( r => 1, g => 1, b => 1 );
+}
+
+my @hexDigit = split //, '0123456789abcdef';
+my %hexValue =
+ map( ( lc( $hexDigit[$_] ) => $_, uc( $hexDigit[$_] ) => $_ ), 0 .. 15 );
+
+sub fromHexTriplet {
+ my ( $class, $triplet ) = @_;
+
+ my @d = $triplet =~ /^#?(.)(.)(.)(.)(.)(.)$/
+ or die "'$triplet' is not a valid colour triplet";
+
+ 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 toHSL {
+ my $self = shift;
+
+ my ( $m, $M ) = minmax( $self->r, $self->g, $self->b );
+
+ 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 contrastWith {
+ my ( $self, $ref ) = @_;
+
+ my $myL = $self->relativeLuminance;
+ my $refL = $ref->relativeLuminance;
+
+ my $ratio = ( $myL + 0.05 ) / ( $refL + 0.05 );
+ $ratio = 1 / $ratio if $ratio < 1;
+ return $ratio;
+}
+
+package MAIN;
+
+use Math::Trig;
+use File::Basename qw(basename dirname);
+use File::Temp qw(tempfile);
+use Getopt::Long;
+
+my $opt_night;
+
+GetOptions(
+ 'night!' => \$opt_night,
+) or exit 1;
+
+my $DEFAULT_HUE = 261.2245;