Create rainbow text

Your challenge is to take input as a line of text and output it like this.

Input / output

The input will be a string that contains only printable ASCII characters. The first or last characters will never be spaces, and there will never be two spaces in a row. It will always be at least two characters long.

Your output should be the same string, converted to rainbow colors as will be described below. The output may be in image form (saved to a file or somehow otherwise made available), or it may simply display the result on the screen (as the reference implementation below does).

Conversion

To determine what color each letter in the string should become, use the following algorithm. Note that each letter is its own individual color. This is not a gradient!

• If this character is a space:

• ... it doesn't matter, because spaces can't really... have a color anyway. Simply output a space.
• Otherwise:

• Let i = the index of this character in the string (0-based, so for the very first letter, this is 0), not counting spaces. For example, in the string foo bar, this value would be 4 for the a. In other words, this is how many non-spaces have been encountered so far.

• Let n = the number of non-spaces in the string.

• The color of this letter can now be expressed, in the HSL cylindrical-coordinate system, as [hue=(i/n)*360°, saturation=100%, lightness=50%].

Note that these directions imply that the output for foo and f oo should be exactly the same, except for an added space after the f. That is, all the letters should retain the same colors.

Further rules for the conversion process are described below, in the Rules section.

Reference implementation

This is written in JavaScript, and you can try it by pressing the "Run code snippet" button.

window.addEventListener('load', function() {
addRainbow('Your challenge is to take input as a line of text and ' +
'output it like this.');
});

// append this text rainbow-ified to the argument (document.body by default)
(el || document.body).appendChild(makeRainbow(text));
}

// returns a <div> that contains the text in a rainbow font
function makeRainbow(text) {
var div = document.createElement('div');
var letterCount = text.replace(/ /g, '').length, spaceCount = 0;
text.split('').forEach(function(letter, idx) {
if (letter == ' ') ++spaceCount;
div.appendChild(makeLetter(letter, (idx - spaceCount) / letterCount));
});
return div;
}

// returns a <span> that contains the letter in the specified color
function makeLetter(letter, hue) {
hue = Math.floor(hue * 360);
var span = document.createElement('span');
span.appendChild(document.createTextNode(letter));
span.style.color = 'hsl(' + hue + ', 100%, 50%)';
return span;
}

Rules

• When computing the Hue value of a letter, you will almost certainly get a decimal (non-integer) number. You may round this to the nearest integer, floor it, take the ceiling, or simply not round at all.

• The font size must be readable. Here, this is defined as a 10pt size font or greater.

• You may use a fixed-width canvas or "drawing area" to output the text, but it must be able to fit the example given in the very first sentence of this post.

• Scoring is , so the shortest code in bytes will win.

• Can the output be a Data URI? That's the output from the HTML canvas Aug 21 '15 at 2:35
• @vihan Yes, that qualifies as per the "The output may be in image form (saved to a file or somehow otherwise made available)" rule.
– Doorknob
Aug 21 '15 at 2:50
• How do you determine whether a coloration meets the spec? Can you specify precisely what conversion formula one should use if only RGB colors are supported in a language? Also, how many bits of precision per channel are necessary? Presumably 8 would be OK, but how about 4, or 1? Aug 21 '15 at 7:28
• @feersum To convert to RGB, you may use a builtin or one of the methods described here. Could you clarify what you mean by your second question? Are you asking about this specifically in the context of HSL to RGB conversion, or in general?
– Doorknob
Aug 21 '15 at 11:08
• Dang, I'm not even going to try with PowerShell ... You only get 16 colors to play with (and they're not even ordered ... rainbow or RGB or otherwise ... just an arbitrary hex value). Reference, with Pictures Really cool challenge, though! Aug 21 '15 at 16:00

Perl, 95

92 bytes code + 3 for -p

This script uses an approximation of the colours available to terminal (256 maximum) currently only including a few colour points selected from this list, so it is likely not to spec, but this was fun anyway! I filtered the list to only show colours with S and L values of 100% and 50% respectively, then sorted by hue, packed the numbers into a string and select the colours from that list.

This implementation includes non-printable characters! Stole @edc65's idea of only replacing \S instead of ., simple, but clever!

@c=map ord,"..........vR../012.3-'!..9]........"=~/./g;s|\S|\e[38;5;$c[@c*$i++/y/!-~//]m$&|g  Hexdump: 0000000: 4063 3d6d 6170 206f 7264 2c22 09c4 cad0 @c=map ord,".... 0000010: d6dc 0be2 be9a 7652 0a2e 2f30 3132 0e33 ......vR../012.3 0000020: 2d27 211b 1539 5d81 a50d c9c8 c7c6 c522 -'!..9]........" 0000030: 3d7e 2f2e 2f67 3b73 7c5c 537c 5c65 5b33 =~/./g;s|\S|\e[3 0000040: 383b 353b 2463 5b40 632a 2469 2b2b 2f79 8;5;$c[@c*$i++/y 0000050: 2f21 2d7e 2f2f 5d6d 2426 7c67 /!-~//]m$&|g


Reverse the hexdump by copying the above text and running:

xxd -r > rainbowtext.pl


pasting in the data and pressing Ctrl+D.

Run using:

perl -p rainbowtext.pl <<< 'Your challenge is to take input as a line of text and output it like this.'


It produces output like:

Python 2: 240 using PIL and colorsys lib

import PIL,colorsys as c
s=input()
u,a=len(s),255
g=Image.new('RGB',(u*6,13),(a,)*3)
[ImageDraw.Draw(g).text((j*6,0),s[j],fill=tuple(int(h*a)for h in c.hls_to_rgb(1.*(j-s[:j].count(' '))/(u-s.count(' ')),.5,1)))for j in range(u)]
g.show()


Example output:

Thanks to @agtoever and @Trang Oul for some golfing tips, and for @Mauris for pointing out the spaces requirement.

To add a true type fonts, font size control, including horizontal offset and color change based on length.

import PIL as P,colorsys as c
s=input()
u=len(s)
a=255
fs=25
f=P.ImageFont.truetype("a.ttf",fs)
sza=f.getsize(s)
oa=f.getoffset(s)
g=P.Image.new('RGB',(sza[0]+fs,2*sza[1]+oa[1]),(a,)*3)
r=fs/4
P.ImageDraw.Draw(g).text((r,0),s,fill=(0,0,0),font=f)
for j in range(u):
o=f.getoffset(s[j])
sz=f.getsize(s[j])
r+=o[0]
P.ImageDraw.Draw(g).text((r,0+fs),s[j],fill=tuple([int(h*a)for h in c.hls_to_rgb(1.*r/sza[0],.5,1)]),font=f)
r+=sz[0]
g.save('a.png')
g.show()


The font I used is available from here: The result is (the top is just printing the string, the one below is printing per letter):

• Drop as P and write out PIL twice and win one character. Change img to i and win 4 more characters. Aug 21 '15 at 7:31
• Save 255 to a variable, replace all ocurrences and use (a,)*3 as white colour. Replace float(j) by (j+.0). Aug 21 '15 at 7:48
• Also: replace float(j) to 1.*j Aug 21 '15 at 7:52
• Assign multiple variables simultaneously (e.g. u=len(s) a=255 => u,a=len(s),255). Remove [] brackets from tuple, they're not needed. Replace loop by list comprehension (method will be evaluated as a side effect). Aug 21 '15 at 9:53
• @willem: Take a look at the problem specification (under Conversion); it specifically explains how your program should handle spaces.
– Lynn
Aug 22 '15 at 5:00

JavaScript (ES6), 114 117 125

Edit2 3 bytes saved thx @Dom Hastings Edit Invalid HTML, but working anyway.

Usual note: test running the snippet on an EcmaScript 6 compliant browser (notably not Chrome not MSIE. I tested on Firefox)

F=s=>document.write(s.replace(/\S/g,c=><b style=color:hsl(${i++/s.replace(/ /g,'').length*360},100%,50%>+c,i=0)) <input value='Your challenge is to take input as a line of text and output it like this.' id=I size=100> <button onclick='F(I.value)'>-></button> • No browser is completely ES6 compliant, yet. IE isn't even fully ES5 compliant yet! Aug 21 '15 at 12:42 • @SuperJedi224 I agree. I'm just saying: don't try it with Chrome or MSIE, while using Firefox it works Aug 21 '15 at 12:47 • I feel like I shouldn't even say anything because I've only just got mine under yours, but I think you can omit the ) after your hsl( declaration as that appears to still work for me in Firefox! Aug 21 '15 at 15:37 • Also, you can change ${c}' to '+c to save 2 more... Better get back on with mine! Aug 21 '15 at 15:47
• This does not work on Safari 9.0. c: Oct 29 '15 at 9:05

Python 3, 131 bytes

Simular to Dom Hastings' answer but implemented in python.

The string '|\x82\x88\x8ejF"#$%\x1f\x19\x137[\x7f~}' was built form the list [124,130,136,142,106,70,34,35,36,37,31,25,19,55,91,127,126,125] is the terminal colour codes to display in order. They have been filtered so they only include colours with saturation 100% and value 50%. The list was then sorted so the correct hues were displayed first. Takes input from stdin and returns it to stdout. Your terminal you are using MUST support ANSI escape codes to run this properly. x=input();u=u'|\82\88\8ejF"#$%\1f\19\137[\7f~}';j=0
for i in x:print('\033[38;5;%dm%s'%(ord(u[j*18//len(x.replace(" ", ""))]),i),end="");j+=i!=" "


Or shortened version with literal byte characters (Didn't paste properly):

x=input();u='|<82><88><8E>jF"#$%^_^Y^S7[^?~}';j=0 for i in x:print('ESC[38;5;%dm%s'%(ord(u[(j*18)//len(x.replace(" ", ""))]),i),end="");j+=i!=" "  Literal hexdump: 783d696e70757428293b753d277c82888e6a46222324251f1913375b7f7e7d273b6a3d300a666f72206920696e20783a7072696e7428271b5b33383b353b25646d25732725286f726428755b286a2a3138292f2f6c656e28782e7265706c616365282220222c20222229295d292c69292c656e643d2222293b6a2b3d69213d2220220a  Thanks @swstephe for saving 9 bytes (and also making me notice my byte counting was ever so slightly very wrong)! • Why not replace "\x82" with the actual character? I wrote that string to a file and read it back into my script in binary. You could replace \033 with a raw escape character, or even \1f (hex). You declare "u", then only use it once. You can save a few characters by moving it down into the expression. You can avoid the int() call by using "//" for integer divide. Aug 27 '15 at 17:06 • oops, \033 is the same as \x1f. Aug 27 '15 at 17:49 PHP, 165 bytes Run with input as the parameter "s" The HTML is invalid but it should render in all major browsers (tested in Chrome and Firefox) <?php$n=preg_match_all("/[^ ]/",$q=$_GET['s']);for($i=$j=0;$j<strlen($q);$j++){if(" "!=$s=$q[$j])$i+=360;echo"<a style='color:hsl(".floor($i/$n).",100%,50%)'>".$s;}


PHP 4.1, 112103 102 bytes

I've used @DankMemes' answer as a starting point. From there on, I've implemented a ton of changes, to the point that the code is different.

The implementation is similar, the code is totally different.

foreach(str_split($s)as$c)echo"<a style=color:hsl(",((" "^$c?$i+=360:$i)/strlen($s))|0,",100%,50%>\$c";


To use it, simply set a value on a SESSION/GET/POST/COOKIE with the name s.

Result of running this function, on the test sentence:

<a style=color:hsl(4,100%,50%>Y<a style=color:hsl(9,100%,50%>o<a style=color:hsl(14,100%,50%>u<a style=color:hsl(19,100%,50%>r<a style=color:hsl(24,100%,50%> <a style=color:hsl(29,100%,50%>c<a style=color:hsl(34,100%,50%>h<a style=color:hsl(38,100%,50%>a<a style=color:hsl(43,100%,50%>l<a style=color:hsl(48,100%,50%>l<a style=color:hsl(53,100%,50%>e<a style=color:hsl(58,100%,50%>n<a style=color:hsl(63,100%,50%>g<a style=color:hsl(68,100%,50%>e<a style=color:hsl(72,100%,50%> <a style=color:hsl(77,100%,50%>i<a style=color:hsl(82,100%,50%>s<a style=color:hsl(87,100%,50%> <a style=color:hsl(92,100%,50%>t<a style=color:hsl(97,100%,50%>o<a style=color:hsl(102,100%,50%> <a style=color:hsl(107,100%,50%>t<a style=color:hsl(111,100%,50%>a<a style=color:hsl(116,100%,50%>k<a style=color:hsl(121,100%,50%>e<a style=color:hsl(126,100%,50%> <a style=color:hsl(131,100%,50%>i<a style=color:hsl(136,100%,50%>n<a style=color:hsl(141,100%,50%>p<a style=color:hsl(145,100%,50%>u<a style=color:hsl(150,100%,50%>t<a style=color:hsl(155,100%,50%> <a style=color:hsl(160,100%,50%>a<a style=color:hsl(165,100%,50%>s<a style=color:hsl(170,100%,50%> <a style=color:hsl(175,100%,50%>a<a style=color:hsl(180,100%,50%> <a style=color:hsl(184,100%,50%>l<a style=color:hsl(189,100%,50%>i<a style=color:hsl(194,100%,50%>n<a style=color:hsl(199,100%,50%>e<a style=color:hsl(204,100%,50%> <a style=color:hsl(209,100%,50%>o<a style=color:hsl(214,100%,50%>f<a style=color:hsl(218,100%,50%> <a style=color:hsl(223,100%,50%>t<a style=color:hsl(228,100%,50%>e<a style=color:hsl(233,100%,50%>x<a style=color:hsl(238,100%,50%>t<a style=color:hsl(243,100%,50%> <a style=color:hsl(248,100%,50%>a<a style=color:hsl(252,100%,50%>n<a style=color:hsl(257,100%,50%>d<a style=color:hsl(262,100%,50%> <a style=color:hsl(267,100%,50%>o<a style=color:hsl(272,100%,50%>u<a style=color:hsl(277,100%,50%>t<a style=color:hsl(282,100%,50%>p<a style=color:hsl(287,100%,50%>u<a style=color:hsl(291,100%,50%>t<a style=color:hsl(296,100%,50%> <a style=color:hsl(301,100%,50%>i<a style=color:hsl(306,100%,50%>t<a style=color:hsl(311,100%,50%> <a style=color:hsl(316,100%,50%>l<a style=color:hsl(321,100%,50%>i<a style=color:hsl(325,100%,50%>k<a style=color:hsl(330,100%,50%>e<a style=color:hsl(335,100%,50%> <a style=color:hsl(340,100%,50%>t<a style=color:hsl(345,100%,50%>h<a style=color:hsl(350,100%,50%>i<a style=color:hsl(355,100%,50%>s<a style=color:hsl(360,100%,50%>.