# Cubify This! A lesson in grayscale… er… color… er… whatever

Being a big fan of the Rubik's cube and of cool art, I've been working on combining the two together to do some really cool stuff. Basically solving miniature Rubik's cubes to form rudimentary pixels in the formation of Rubik's cube art. Examples of such art can be seen via this link: http://google.com/search?q=rubik%27s+cube+art

Now, the purpose of this Code Golf is to create code that accepts an image as input, and then converts it in the following manner:

The image is initially reduced to web-safe grayscale colors. The reason behind this is because we need to isolate the web-safe grayscale palette (i.e. 000000, 333333, 666666, 999999, CCCCCC, and FFFFFF). An algorithm on the colorimetric method of converting to grayscale is available at: http://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale, should you wish to use that as inspiration.

One would then render out the grayscale to the appropriate colors. To break it down quickly: 000000 will refer to Rubik's blue, 333333 will refer to Rubik's red, 666666 will refer to Rubik's green, 999999 will refer to Rubik's orange, CCCCCC will refer to Rubik's yellow, and FFFFFF will refer to Rubik's white.

I would rather that your resulting code can render from the photo's palette straight to Rubik's colors. The two-stage method from converting to web-safe grayscale and then to the corresponding Rubik's palette is just to give you an idea on the logic behind the process, but if it is easier for you to do this, by all means do so.

The actual RGB values for the Rubik's palette must correspond to the following:

• Red: #C41E3A
• Green: #009E60
• Blue: #0051BA
• Orange: #FF5800
• Yellow: #FFD500
• White: #FFFFFF

To give you an example, I have cropped Abraham Lincoln's head from the following picture: , and rendered the algorithm to produce the following:

The grid is there so that you can see how each individual miniature Rubik's cube would have to be configured to make up the image. The true size of the resulting image is 45 pixels by 45 pixels, meaning (45/3) * (45/3) = 15 * 15 = 225 miniature Rubik's cubes would be used to make this image. I am not expecting you to present the resulting image with a grid as I have.

So this is what's required:

1. The image to be processed by this algorithm must be x pixels wide by y pixels high, such that x and y are multiples of 3. This is to aid with the ease of rendering as part of a Rubik's cube mosaic. If your image is quite large, reducing it to something around the 45 x 45 to 75 x 75 or thereabouts in dimensions prior to processing is advised. Bear in mind, this resizing component is OPTIONAL.

2. The image needs to be converted to the sextacolored Rubik's cube palette to create the mosaic.

3. The resulting image needs to be a valid graphic file after processing. To prove your code works, run it against an image of one of the presidents of the United States of America, or a well known Hollywood celebrity. I have already used Abraham Lincoln in my example, so this president can no longer be used. Ensure that you provide the language you have used, the byte count as well as the president/celebrity used to test your code, including before and after shots...

4. Each entry must have a unique president/celebrity as their test case. I will not accept duplicates. This will ensure that duplicate results are not used to test different code entries. It's all very well to say that your code works, it's another thing to prove it.

5. Shortest code wins.

I'm changing this to a popularity contest... I'd rather see who can do this without having to compete on byte count... So I'll award this along with a bounty after the 28th of February, 2014.

• I think it would be better if you'd add the Rubik RGB values to the post instead of relying on a link. – SztupY Feb 16 '14 at 11:27
• Does "image to be processed must be x pixels wide by y pixels high" mean that resizing is part of code or that image is pre-processed to required size? – user2846289 Feb 16 '14 at 12:31
• Are there any impossible states of a Rubik's Cube if you only constrain one face? – Nick T Feb 16 '14 at 17:40
• @WallyWest You would LOVE my app MineCam, it does this, but rather than making squares, it uses mine craft blocks, and it also does it 15 times per second with the real time iPhone camera, thus converting the whole world around you into a mine craft universe. itunes.apple.com/us/app/minecam/id675845303?mt=8 (If only it could also generate a seed for said world hahahaha) – Albert Renshaw Feb 16 '14 at 23:21
• @WallyWest: it's not about lazyness. The problem should give you all information you need to get started, even if the rest of the internet is down. In a year or two, that link might be taken down and noone will update the link. If you provide enough information about how to create web-safe grayscale colors (which is not necessary to solve the problem) you could've just easily added a small mapping table, like #000000 => #0051BA, etc. – SztupY Feb 17 '14 at 1:28

# Imagemagick (108)

Version: ImageMagick 6.8.7-7 Q16 x86_64 2013-11-27

The following call:

$convert -resize 75x75 -fx "q=p.intensity;q<1/6?#0051BA:q<2/6?#C41E3A:q<3/6?#009e60:q<4/6?#ff5800:q<5/6?#FFD500:#FFF" input output  where input and output have to be modified for the input and output filename. I only counted the chars between -resize and #FFF", if you think this is invalid feel free to comment. I used Lenna as image (she appeared in a Playboy, and anyone doing that should count as a Hollywood celebrity, right?) Input: Output: $ convert -resize 75x75 -fx "q=p.intensity;q<1/6?#0051BA:q<2/6?#C41E3A:q<3/6?#009e60:q<4/6?#ff5800:q<5/6?#FFD500:#FFF" Lenna.png LennaRubik.png


Output enlarged:

Notes: according to the imagemagick docs you cannot have more than one conditional operator in a statement, but the call still seems to work fine, so probably this was fixed and the docs were just not updated.

Running identify on the result image (to show the colors are indeed fine):

\$ identify -verbose LennaRubik.png
(...)
Colors: 6
Histogram:
338: (  0, 81,186) #0051BA srgb(0,81,186)
1652: (  0,158, 96) #009E60 srgb(0,158,96)
1187: (196, 30, 58) #C41E3A srgb(196,30,58)
1674: (255, 88,  0) #FF5800 srgb(255,88,0)
706: (255,213,  0) #FFD500 srgb(255,213,0)
68: (255,255,255) #FFFFFF white
(...)


If you think Lenna doesn't count as a proper celebrity, here is Bruce Willis:

• +1 I think that your answer is almost unbeatable (or even unbeatable at all). I will just suggest that you take some unquestionably Hollywood celebrity or american president photo and add to this (don't need to remove Lenna, keep both). Otherwise some boring people might complain and downvote just because that. – Victor Stafusa Feb 16 '14 at 12:09
• @Victor: I think Mathematica, Matlab or Octave might easily beat this, as the conditions inside the fx part can be compressed even further in a language that has better expressivity. And those languages also have native image support (so no chars are lost by needing to import imagemagick/gd/etc.) – SztupY Feb 16 '14 at 12:15
• @SztupY I know of Lenna very well... I'll count that... Nice work with Bruce Willis too... – Eliseo D'Annunzio Feb 16 '14 at 23:12
• Lenna is cute(r). Up. – blabla999 Feb 17 '14 at 3:42
• +1 for using the right tool for the job. To my understanding, the right way to use imagemagick is to call the image first, than the options, than the output file. – CousinCocaine Feb 27 '14 at 22:01

# Mathematica

We'll work with a square region from a U.S. stamp featuring Greta Garbo. It will be referred to as j.

f[i_,rs_,m_:True]:=
Module[{(*rs=rastersize-4*)r={0.77,0.12,0.23},gr={0,0.62,0.38},b={0,0.32,0.73},o={1,0.35,0},y={1,0.84,0},w={1,1,1},
c1={r,gr,b,o,y,w},grayGreta,garboColors},
grayGreta=(*Reverse@*)ImageData[ColorQuantize[Rasterize[i,(*ImageResolution \[Rule]15*)RasterSize->rs+1,ImageSize->rs],6]];
garboColors=Union@Flatten[grayGreta,1];
Mesh->If[m==True,{rs-1,rs-1},None],MeshStyle->Black]]


The function f takes 3 parameters:

• i which refers to the image
• rs, the raster size
• m, a boolean variable that states whether Mesh lines should be used. (The default setting is True).

Using raster sizes of 15, 30, 45, and 75:

GraphicsGrid[{{f[j, 15], f[j, 30]}, {f[j, 45], f[j, 75]}}, ImageSize -> 800]


I can't imagine anyone making a Rubrik's cube with so many pieces! Interesting exercise nonetheless.

## Playing around with colors

This is from an earlier entry. The code is slightly different. Graphics is used instead of ArrayPlot. Also, we use the full stamp even though it is not square.

There are 6!=720 permutations of the Rubrik cube colors.

The following displays the center image of the upper row (set 6 images below). When the grayscale values are arranged from darkest to lightest, the colors are {r,gr,b,o,y,w}. Other variations nonetheless work.

i is the original image in grayscale.

Graphics[Raster[(g=Reverse@ImageData[ColorQuantize[Rasterize[i,RasterSize->75],6]])


i is the original grayscale image of the full Greta Garbo stamp.

Rasterize[garbo,RasterSize->75 rasterizes the image into a 75 by 75 array.

ColorQuantize[<>, 6] reduces the grayscale values to a set of 6.

ImageData retrieves the data array from the image; it comes upside-down.

Reverse flips the data array, hence the picture, right-side up.

garboColors are the 6 grayscale values in the quantized image.

Graphics[Raster displays the final image.

rubrikColors are RGB values of the 6 Rubrik cube colors.

Various color permutations of Red, Green, Blue, Orange, Yellow, and White are given.

r={0.77,0.12,0.23};gr={0,0.62,0.38};b={0,0.32,0.73};o={1,0.35,0};y={1,0.84,0};w={1,1,1};
c1={r,gr,b,o,y,w};
c2={r,b,gr,o,y,w};
c3={b,r,gr,o,y,w};
c4={gr,b,r,o,y,w};
c5={b,r,gr,y,o,w};


And the code:

grayGreta=Reverse@ImageData[ColorQuantize[Rasterize[i,RasterSize->75],6]];
garboColors=Union@Flatten[grayGreta,1];
Grid[{{i,


## Garbos Galore

Here are 72 (of the 720) images of Greta Garbo that use the 6 Rubrik cube colors. Some images work better than others, don't you think?

GraphicsGrid@Partition[(Graphics[Raster[grayGreta /. Thread[garboColors -> #]]] &
/@ Take[Permutations[{r, gr, b, o, y, w}, {6}], 72]), 12]


• Greta, oh Greta... This turned out better than I expected. @DavidCarraher, nice work here... – Eliseo D'Annunzio Feb 16 '14 at 23:14
• @WallyWest. Thanks. It was a very interesting challenge. – DavidC Feb 16 '14 at 23:57
• I was so sure Mathematica would beat imagemagick, can't this be golfed even further? Are all those functions requred? – SztupY Feb 17 '14 at 1:05
• @SztupY Half the code is dedicated to getting the colors right. Reverse could be eliminated, leaving the picture upside down, but I don't see any other opportunities. Mathematica is expressive but uses big words. Someone adept at images could probably reduce the code size a bit but I doubt they could beat your imagemagick code. – DavidC Feb 17 '14 at 1:17
• There were indeed some inconsistencies across the code. I hope they are now gone. i hold the original image. gr stands for Rubrik's green. g refers to the rasterized and quantized image data for the grayscale image. – DavidC Feb 17 '14 at 13:33

# Smalltalk (Smalltalk/X), 289 139*

input: i; output: r

r:=i magnifiedTo:75@75.
r colorMapProcessing:[:c||b|b:=c brightness.Color rgbValue:(#(16r0051BA 16rC41E3A 16r009e60 16rff5800 16rFFD500 16rFFFFFF)at:(b*6)ceiling)]


input:

output:

enlarged:

(for all youngsters: this is NOT Madonna ;-)

[*] I have not counted the magnification to 75x75 (the first line) - could have used an already resized as input. Hope that is ok with you.

• I adore Marilyn Monroe... Great choice... Resizing was an optional feature... – Eliseo D'Annunzio Feb 16 '14 at 23:15

## Postscript, and TRUE Rubik colors! ;-)

Well, this solution is a bit of off-topic here, as it's restricted to somewhat highly specialized sphere. But after much frustration with e.g. "weird numbers question" (being unable to produce something practically working) I decided to publish something and so pulled this out of my pile of half-finished scribbles and made it presentable.

The solution exploits the fact, that 1st revision of this question defines required colors by link to a site, which clearly states, that Pantone(R) colors are to be used, and RGB colors are approximations only. Then I thought, why would I do approximations, when I can do genuine color? -:)

10 dict begin
/Size 75 def
/Names  [(PMS 012C) (PMS 021C) (PMS 347C)   (PMS 200C)    (PMS 293C)   ] def
/Colors [[0 .16 1 0][0 .65 1 0][1 0 .39 .38][0 .9 .72 .28][1 .56 0 .27]] def
<</PageSize [Size dup]>> setpagedevice
Size dup scale
true setoverprint
(%stdin) (r) file 100 string readline pop
(r) file <</CloseSource true>>/DCTDecode filter
0 1000000 string
dup <</CloseTarget true>>/NullEncode filter
{
{
4 -1 roll 1 add 4 1 roll
{} forall
1 index exch write
} {pop exit} ifelse
} loop
closefile
0 3 -1 roll getinterval
exch closefile
/data exch def
/n data length sqrt cvi def
1 1 Names length {
/N exch def
{
dup N Names length 1 add div gt
{1} {0} ifelse}
{pop 1}
ifelse
} settransfer
[/Separation Names N 1 sub get /DeviceCMYK {
Colors N 1 sub get
{ 1 index mul exch } forall pop
}] setcolorspace
<<
/ImageType        1
/Width            n
/Height           n
/ImageMatrix      [n 0 0 n neg 0 n]
/BitsPerComponent 8
/Decode           [0 1]
/DataSource       data
>> image
} for
showpage
end


This code is to be saved as e.g. rubik.ps and then fed to Ghostscript with usual incantation:

gs -q -sDEVICE=psdcmyk -o out.psd rubik.ps


It then waits you on the next line, for input of JPG file name e.g.

kelly.jpg


and, if all goes well, saves the output to out.psd file.

Input must be square RGB JPEG (any size), output is PSD with spot color channels. You'll need Photoshop to view the file. Changing GS device from psdcmyk to anything else will produce nothing usable. JPEG as input - because postscript interpreter can decode data stream from it directly. Square shape - because the program relies on sqrt of string length to find width (and height) of the image.

First lines define output image size (can be changed from default 75) and the color palette (colors and their number can be changed too). Anything else is not hard-coded, I think.

What's going on? Stream of RGB triplets is converted on the fly to the string of grayscale values (with simple formula), usual 8-bit contone image dictionary is built and used to paint 5 identical images on top of each other in 5 Separation color spaces. The trick is to apply transfer functions before each invocation of image operator. E.g. for yellow paint this function returns 0 for input values in 0.167 .. 0.333 range only, and 1 otherwise.

Input:

Screenshot of output 75x75 open in Photoshop, enlarged 800%:

And Photoshop channels palette:

• +1 for using Grace Kelly... you have my complete respect... – Eliseo D'Annunzio Feb 27 '14 at 23:47

# Brainfuck

++++[->+[,.----------]<]>>>>---->->++++++++++>>------------>+++>+++>--
--->++++++[->+++++<]---->+[-<+++++++<+++<+++++<+++<+++<++++++<++++++<+
<++>>>>>>>>>]<[<]<<,+[,<++++++>[>++++++[->+++++++<]>+[<<[->]>[<]>-]<<<
->]+<[-[-[-[-[[-].>>>>>>>>.<.<<<<<<-<]>[->>>>>[.<]<<]<]>[-.>>>[>]<<<.<
.[<]<<]<]>[--.+>>>[>]<<.[<].<<]<]>[--.+>>>[>]<.[<].<<]<]>[--...+],,+]


This requires a BF interpreter/compiler that has -1 as EOF and that has higher than 8 bit cells IF one of the red pixels are 255. Or else it will stop premature since it won't be able to differ between EOF and value 0xFF. With jitbf you have whatever the machine has as integer size and can do this to force -1 as EOF:

jitbf --eof -1 rubiks.bf < angelina.pnm > angelina-rubix.pnm


The image file format rendered is the full RGB PNM file (P6), raw as option in Gimp.

It uses the green channel only (which is one of many ways to convert a color image to greyscale). It reduces the value by 43 without reduceing the value below zero to find out which rubiks color to use and have a switch that prints out the correct RBG color that corresponds.

Image of Angelina Jolie from Hackers (1995) scaled down to 75x75 and processed with the application:

Same, only I used the original size:

And since I'm psychic here's a president as well:

• Off topic, but todays xkcd also has references to Hackers (1995) – Sylwester Mar 3 '14 at 13:46
• This one also does: xkcd.com/1247 – Shade May 16 '14 at 16:41

# C#

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;

class Program
{
static void Main(string[] args)
{
unchecked
{
var t = new[] { 0xFFC41E3A, 0xFF009E60, 0xFF0051BA, 0xFFFF5800, 0xFFFFD500, 0xFFFFFFFF }.Select(v => Color.FromArgb((int)v)).ToArray();
var o = new Bitmap(Bitmap.FromFile(args[1]));
var m = double.Parse(args[0]);
var r = Math.Min(m / o.Width, m / o.Height);
var w = (int)(o.Width * r);
var h = (int)(o.Height * r);

o = new Bitmap(o, w - (w % 3), h - (h % 3));
for (int y = 0; y < o.Height; y++)
for (int x = 0; x < o.Width; x++)
o.SetPixel(x, y, N(o.GetPixel(x, y), t));
o.Save(args[2], ImageFormat.Png);
}
}

static Color N(Color c, Color[] t)
{
return t.OrderBy(v => Math.Abs(W(v) - W(c))).First();
}

static double W(Color c)
{
return .11 * c.B + .59 * c.G + .30 * c.R;
}
}


You need to run it with 3 parameters:

foo.exe 75 d:\test.jpg d:\out.png

Where 75 is max. width/height, d:\test.jpg is input file and d:\out.png is the output file.

Output for various images in this contest:

My own celebrity:

Output:

However, other (larger than 75x75) sizes do result in better images:

And, if we stick with presidents:

Since this is no (longer?) codegolf I didn't bother to "minimize" the code too much. Also, since the instructions did not specifically mention the image had to be the same width as height (square) I didn't bother with cropping; I do, however, make sure the image is a multiple of 3 pixels wide/high. If you want square images, use square inputs Finally; the algorithm is far from optimal.

A few more (since people upvote hot chicks / internet heroes more )

# Objective-C

I saw this challenge last night and had an ever so slightly confusing time with -[NSArray indexOfObject:inSortedRange:options:usingComparator:], hence the delay.

- (UIImage  *)rubiksImage:(UIImage *)inputImg
{
//Thank you http://stackoverflow.com/a/11687434/1153630 for the greyscale code
CGRect imageRect = CGRectMake(0, 0, inputImg.size.width, inputImg.size.height);

int width = imageRect.size.width;
int height = imageRect.size.height;

uint32_t *pixels = (uint32_t*)malloc(width * height * sizeof(uint32_t));

memset(pixels, 0, width * height * sizeof(uint32_t));

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(pixels, width, height, 8, width * sizeof(uint32_t), colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedLast);

CGContextDrawImage(context, imageRect, [inputImg CGImage]);

const int RED = 1;
const int GREEN = 2;
const int BLUE = 3;

for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
uint8_t* rgbaPixel = (uint8_t*)&pixels[y * width + x];
uint32_t grayPixel = 0.3 * rgbaPixel[RED] + 0.59 * rgbaPixel[GREEN] + 0.11 * rgbaPixel[BLUE];

NSArray *r = [self rubixColorFromGrey:grayPixel];

rgbaPixel[RED] = [r[2] integerValue];
rgbaPixel[GREEN] = [r[1] integerValue];
rgbaPixel[BLUE] = [r[0] integerValue];
}
}

CGImageRef newCGImage = CGBitmapContextCreateImage(context);

CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
free(pixels);

UIImage* newUIImage = [UIImage imageWithCGImage:newCGImage];

CGImageRelease(newCGImage);

return newUIImage;
}

- (NSArray *)rubixColorFromGrey:(uint32_t)p
{
NSArray *colors = @[@0, @51, @102, @153, @204, @255];

NSUInteger index = [colors indexOfObject:@(p)
inSortedRange:NSMakeRange(0, colors.count)
options:NSBinarySearchingInsertionIndex | NSBinarySearchingFirstEqual
usingComparator:^(id a, id b) {
return [a compare:b];
}];
switch (index) {
case 0:
return rgb(0, 81, 186);
break;
case 1:
return rgb(196, 30, 58);
break;
case 2:
return rgb(0, 156, 96);
break;
case 3:
return rgb(255, 82, 0);
break;
case 4:
return rgb(255, 213, 0);
break;
case 5:
return rgb(255, 255, 255);
break;

default:
break;
}

return colors; //go wild
}

NSArray *rgb(int r, int g, int b)
{
return @[@(r), @(g), @(b)];
}


I ran it on my iPad like so:

UIImageView *img = [[UIImageView alloc] initWithImage:[self rubiksImage:[UIImage imageNamed:@"danny.png"]]];
[img setCenter:self.view.center];


Before After

Before After

# Python

Format: python rubik.py <input> <max_cubes> <output>.

Coverts pixel to grayscale using suggested algorithm.

import Image, sys

def rubik(x, max_cubes = 25):

img = x
max_cubes *= 3

if x.size[0] > max_cubes or x.size[1] > max_cubes:

print "Resizing image...",

if x.size[0] > x.size[1]:
img = x.resize((max_cubes, int(max_cubes * float(x.size[1]) / x.size[0])), Image.ANTIALIAS)
else:
img = x.resize((int((max_cubes * float(x.size[0]) / x.size[1])), max_cubes), Image.ANTIALIAS)

if x.size[0] % 3 or x.size[1] % 3:
print "Sizes aren't multiples of 3"
return

print "Image resized to %i x %i pixels" % img.size

out = Image.new('RGB', img.size)

print "Converting image...",

for x in xrange(out.size[0]):
for y in xrange(out.size[1]):
r, g, b = img.getpixel((x, y))
if r == g == b == 255:
out.putpixel((x,y), (255, 255, 255))
else:
l = 0.2126 * r + 0.7152 * g + 0.0722 * b
l /= 255
out.putpixel((x,y), (
(0x00, 0x51, 0xBA),
(0xC4, 0x1E, 0x3A),
(0x00, 0x9E, 0x60),
(0xFF, 0x58, 0x00),
(0xFF, 0xD5, 0x00)
)[int(5 * (l <= 0.0031308 and 12.92 * l  or 1.055 * l ** (1/2.4) - 0.055))])

print "Image converted successfully."

print "Stats:"
h, v = img.size[0] / 3, img.size[1] / 3
print "   ", h, "horiz. Rubik cubes"
print "   ", v, "vert. Rubik cubes"
print "   ", h * v, "total Rubik cubes"

return out.resize((out.size[0], out.size[1]))

if __name__ == "__main__":
rubik(Image.open(sys.argv[1]).convert("RGB"), int(sys.argv[2])).save(sys.argv[3])



Input:

(source: ilmamilio.it)

Output with max_cubes = 25:

Output with max_cubes = 75:

Output with max_cubes = 225:

• Isn't white color missing? And the darkest should be blue, but, as I see now, that's the issue with some other images too. – user2846289 Mar 1 '14 at 20:55
• @VAdimiR Whoops! Mapped them in the wrong order. As for white not showing up, it's because of the FP precision (1.055 - 0.055 = 0.9999999999999999). I think I'll have to hardcode white, which isn't hard, since it'd show up only at an original value of #FFFFFF anyway. – Oberon Mar 1 '14 at 21:32
• About white, my opinion was that 0..1 (lightness) range is divided into 6 parts, and anything 0.83..1.00 is mapped to white, or otherwise there'll be not much sense in making picture of 6 colors of the cube, but that's how I read it. – user2846289 Mar 1 '14 at 21:54
• @Oberon Interesting choice using Pertini... He almost lived to the age of 94... And great work using Python, and I have to admit, not one of the easiest languages I've come across, so well done! – Eliseo D'Annunzio Mar 2 '14 at 9:51

# Piet

It is presented here with a codel size of 20 for readability's sake. To counter that same aspiration, it has been half-heartedly if not golfed then at least been subject to compacting efforts.

Tested with npiet like so:

npiet -q rc.png < infile.ppm > outfile.ppm


Assumes the files are of the "plain" RGB variety (P3), i.e. with the image data as numbers in plaintext and not binary. It ignores the maximum colour value and just assumes that it is 255. In the output, every whitespace is a newline.

Looping over each pixel, the following happens:

1. The colours we are to choose from are pushed to stack, lightest first, so that the darkest colours end up on top.
2. The RGB triplet is read in and converted to greyscale. Since floating point mathematics is not built-in nor trivial to implement, integer maths is used: grey = (red * 299 + green * 587 + blue * 114) / 1000.
3. The greyscale value is divided by 43, to obtain a number 0..5 (colour index = ci).
4. This number is increased by 1, multiplied by 3 and duplicated on stack. The top value is then decreased by 3, giving us two values on top of stack: [(ci - 1) * 3] [ci * 3].
5. These new values are used with the roll instruction, which will - with the parameters given above - bring the desired RGB triplet to the top of the stack. (You can think of it as taking a deck of cards and cutting it so that you hold ci * 3 cards in your hand, and then taking the top card of that little stack you're holding and putting it down on the pile still on the table, then placing what you hold top of that; repeat this (ci - 1) * 3 times.)
6. Triplet is output in the usual way.
7. The unused triplets are popped from stack.
8. If at the end, we exit, otherwise we go back to step 1.

Input:

Output 75x75 (input was scaled before passing it to the program itself):

Output very large indeed: