package DDG::Goodie::ColorCodes; # ABSTRACT: Copious information about various ways of encoding colors. use strict; use DDG::Goodie; use Color::Library; use Color::Mix; use Convert::Color; use Convert::Color::Library; use Convert::Color::RGB8; use Math::Round; use Try::Tiny; my %types = ( # hash of keyword => Convert::Color prefix rgb => 'rgb8', hex => 'rgb8', html => 'rgb8', css => 'rgb8', color => 'rgb8', hsl => 'hsl', hsv => 'hsv', cmy => 'cmy', cmyk => 'cmyk', cmyb => 'cmyk', ); # Eliminate NBS_ISCC sub-dictionaries from our lookups. # They contain "idiosyncratic" color names (including 'email' in NBS_ISCC::M) which will # otherwise cause this to return answers for which no one was looking. my $color_dictionaries = join(',', grep { $_ !~ /^nbs-iscc-/ } map { $_->id } Color::Library->dictionaries); my $typestr = join '|', sort { length $b <=> length $a } keys %types; $typestr =~ s/([#\^\$\*\+\?])/\\$1/g; triggers query_raw => qr/^ (?:what(?:\si|'?)s \s* (?:the)? \s+)? # what's the, whats the, what is the, what's, what is, whats (?:(inverse|negative|opposite)\s+(?:of)?)? (?: (.*?)\s*(.+?)\bcolou?r(?:\s+code)?| # handles "rgb red color code", "red rgb color code", etc (.*?)\s*(.+?)\brgb(?:\s+code)?| # handles "red rgb code", etc (.*?)\s*colou?r(?:\s+code)?(?:\s+for)?\s+(.+?)| # handles "rgb color code for red", "red color code for html", etc (.*?)(rgba)\s*:?\s*\(?\s*(.+?)\s*\)?| # handles "rgba( red )", "rgba:255,0,0", "rgba(255 0 0)", etc ([^\s]*?)\s*($typestr)\s*:?\s*\(?\s*(.+?)\s*\)?| # handles "rgb( red )", "rgb:255,0,0", "rgb(255 0 0)", etc \#?([0-9a-f]{6})|\#([0-9a-f]{3}) # handles #00f, #0000ff, etc ) (?:(?:'?s)?\s+(inverse|negative|opposite))? (?:\sto\s(?:$typestr))? $/ix; zci is_cached => 1; zci answer_type => 'color_code'; primary_example_queries 'hex color code for cyan'; secondary_example_queries 'rgb(173,216,230)', 'hsl 194 0.53 0.79', 'cmyk(0.12, 0, 0, 0)', '#00ff00'; description 'get hex, RGB, HSL and CMYB values for a color'; name 'ColorCodes'; code_url 'https://github.com/duckduckgo/zeroclickinfo-goodies/blob/master/lib/DDG/Goodie/ColorCodes.pm'; category 'conversions'; topics 'programming'; attribution cpan => 'CRZEDPSYC', github => ['http://github.com/mintsoft', 'Rob Emery']; my %trigger_invert = map { $_ => 1 } (qw( inverse negative opposite )); my %trigger_filler = map { $_ => 1 } (qw( code )); my $color_mix = Color::Mix->new; sub percentify { my @out; push @out, ($_ <= 1 ? round(($_ * 100))."%" : round($_)) for @_; return @out; } sub create_output { my (%input) = @_; my ($text, $html) = ("",""); (my $hex_for_links = $input{'hex'}) =~ s/^#//; my $hex = "Hex: ".uc($input{'hex'}); my $rgb = "RGBA(" . join(", ", @{$input{'rgb'}}) . ", ".$input{'alpha'}.")"; my $rgb_pct = "RGB(" . join(", ", @{$input{'rgb_percentage'}}) . ")"; my $hsl = "HSL(" . join(", ", @{$input{'hsl'}}) . ")"; my $cmyb = "CMYB(" . join(", ", @{$input{'cmyb'}}) . ")"; my @analogous_colors = @{$input{'analogous'}}; my $complementary = uc $input{'complementary'}; $text = "$hex ~ $rgb ~ $rgb_pct ~ $hsl ~ $cmyb"."\n" . "Complementary: #$complementary\n" . "Analogous: ".(join ", ", map { "#".uc $_ } @analogous_colors); my $comps = "
" . "" . "
" . "

Complementary #:

" . qq[$complementary] . "

"; my $analogs = "
" . (join "", map { " "; } @analogous_colors) . "
" . "

Analogous #:

" . (join ", ", map { qq[].(uc $_).'' } @analogous_colors) . "

"; $html = "
" . "

$hex

$rgb

$hsl

$cmyb

" . "

Images" . " | " . "Info" . " | " . "Picker

" . "
" . "
" . "
$comps
" . "
$analogs
" . "
"; return ($text, $html); } handle matches => sub { my $color; my $inverse; my $type = 'rgb8'; # Default type, can be overridden below. my @matches = @_; s/\sto\s(?:$typestr)//; foreach my $q (map { lc $_ } grep { defined $_ } @matches) { # $q now contains the defined normalized matches which can be: if (exists $types{$q}) { $type = $types{$q}; # - One of our types. } elsif ($trigger_invert{$q}) { $inverse = 1; # - An inversion trigger } elsif (!$trigger_filler{$q}) { # - A filler word for more natural querying $color = $q; # - A presumed color } } return unless $color; # Need a color to continue! $color =~ s/\sto\s//; my $alpha = "1"; $color =~ s/(,\s*|\s+)/,/g; # Spaces to commas for things like "hsl 194 0.53 0.79" if ($color =~ s/#?([0-9a-f]{3,6})$/$1/) { # Color looks like a hex code, strip the leading # $color = join('', map { $_ . $_ } (split '', $color)) if (length($color) == 3); # Make three char hex into six chars by repeating each in turn $type = 'rgb8'; } elsif ($color =~ s/([0-9]+,[0-9]+,[0-9]+),([0]?\.[0-9]+)/$alpha = $2; $1/e) { #hack rgba into rgb and extract alpha $type = 'rgb8'; } else { try { # See if we can find the color in one of our dictionaries. $color = join(',', Convert::Color::Library->new($color_dictionaries . '/' . $color)->as_rgb8->hex); $type = 'rgb8'; # We asked for rgb8 from our dictionary, so make sure our type matches. }; } my $col = try { Convert::Color->new("$type:$color") }; # Everything should be ready for conversion now. return unless $col; # Guess not. if ($inverse) { # We triggered on the inverse, so do the flip. my $orig_rgb = $col->as_rgb8; $col = Convert::Color::RGB8->new(255 - $orig_rgb->red, 255 - $orig_rgb->green, 255 - $orig_rgb->blue); } my $hex_code = $col->as_rgb8->hex; my $complementary = $color_mix->complementary($hex_code); my @analogous = $color_mix->analogous($hex_code,12,12); @analogous = ($analogous[1], $analogous[11]); my @rgb = $col->as_rgb8->rgb8; my $hsl = $col->as_hsl; my @rgb_pct = percentify($col->as_rgb->rgb); my @cmyk = percentify($col->as_cmyk->cmyk); my %outdata = ( hex => '#' . $hex_code, rgb => \@rgb, rgb_percentage => \@rgb_pct, hsl => [round($hsl->hue), percentify($hsl->saturation), percentify($hsl->lightness)], cmyb => \@cmyk, alpha => $alpha, complementary => $complementary, analogous => \@analogous ); my ($text, $html_text) = create_output(%outdata); return $text, html => '
' . '' . $html_text . "
"; }; 1;