27
\$\begingroup\$

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: enter image description here, and rendered the algorithm to produce the following:

enter image description here

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.

\$\endgroup\$
13
  • 4
    \$\begingroup\$ I think it would be better if you'd add the Rubik RGB values to the post instead of relying on a link. \$\endgroup\$
    – SztupY
    Feb 16, 2014 at 11:27
  • \$\begingroup\$ 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? \$\endgroup\$ Feb 16, 2014 at 12:31
  • \$\begingroup\$ Are there any impossible states of a Rubik's Cube if you only constrain one face? \$\endgroup\$
    – Nick T
    Feb 16, 2014 at 17:40
  • 1
    \$\begingroup\$ @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) \$\endgroup\$ Feb 16, 2014 at 23:21
  • 2
    \$\begingroup\$ @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. \$\endgroup\$
    – SztupY
    Feb 17, 2014 at 1:28

9 Answers 9

17
+50
\$\begingroup\$

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:

Input image

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

Generated image

Output enlarged:

Enlarged image

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:

Bruce Original

Bruce Small

Bruce Large

\$\endgroup\$
6
  • \$\begingroup\$ +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. \$\endgroup\$ Feb 16, 2014 at 12:09
  • \$\begingroup\$ @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.) \$\endgroup\$
    – SztupY
    Feb 16, 2014 at 12:15
  • \$\begingroup\$ @SztupY I know of Lenna very well... I'll count that... Nice work with Bruce Willis too... \$\endgroup\$ Feb 16, 2014 at 23:12
  • 2
    \$\begingroup\$ Lenna is cute(r). Up. \$\endgroup\$
    – blabla999
    Feb 17, 2014 at 3:42
  • \$\begingroup\$ +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. \$\endgroup\$ Feb 27, 2014 at 22:01
15
\$\begingroup\$

Mathematica

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

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];
ArrayPlot[grayGreta/.Thread[garboColors-> RGBColor@@@c1],
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]

4 garbos

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]])
/.Thread[Union@Flatten[g,1]-> {{7,1,2},{0,6,4},{0,3,7},{10,4,0},{10,8,0},{10,10,10}}/10]]]

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,
Graphics[Raster[grayGreta/.Thread[garboColors-> c1]]],
Graphics[Raster[grayGreta/.Thread[garboColors-> c2]]]},
{Graphics[Raster[grayGreta/.Thread[garboColors-> c3]]],
Graphics[Raster[grayGreta/.Thread[garboColors-> c4]]],
Graphics[Raster[grayGreta/.Thread[garboColors-> c5]]]}}]

garbos


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]

garbos galore

\$\endgroup\$
7
  • \$\begingroup\$ Greta, oh Greta... This turned out better than I expected. @DavidCarraher, nice work here... \$\endgroup\$ Feb 16, 2014 at 23:14
  • \$\begingroup\$ @WallyWest. Thanks. It was a very interesting challenge. \$\endgroup\$
    – DavidC
    Feb 16, 2014 at 23:57
  • \$\begingroup\$ I was so sure Mathematica would beat imagemagick, can't this be golfed even further? Are all those functions requred? \$\endgroup\$
    – SztupY
    Feb 17, 2014 at 1:05
  • 1
    \$\begingroup\$ @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. \$\endgroup\$
    – DavidC
    Feb 17, 2014 at 1:17
  • 1
    \$\begingroup\$ 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. \$\endgroup\$
    – DavidC
    Feb 17, 2014 at 13:33
6
\$\begingroup\$

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:

enter image description here

output:

enter image description here

enlarged:

enter image description here

(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.

\$\endgroup\$
1
  • \$\begingroup\$ I adore Marilyn Monroe... Great choice... Resizing was an optional feature... \$\endgroup\$ Feb 16, 2014 at 23:15
4
\$\begingroup\$

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 
{
    3 index 3 string readstring
    {
        4 -1 roll 1 add 4 1 roll
        {} forall
        0.11 mul exch 0.59 mul add exch 0.3 mul add cvi
        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 
            {N 1 add 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:

enter image description here

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

enter image description here

And Photoshop channels palette:

enter image description here

\$\endgroup\$
1
  • 1
    \$\begingroup\$ +1 for using Grace Kelly... you have my complete respect... \$\endgroup\$ Feb 27, 2014 at 23:47
4
\$\begingroup\$

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:

Angelina Jolie 75x75 / Fair Use Angelina Jolie 75x75 in Rubiks cube colors / Fair Use Same scaled 6x

Same, only I used the original size:

Same only not scaled down first / Fair use

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

Arnold Schwarzenegger CC from Wikipedia

\$\endgroup\$
2
  • \$\begingroup\$ Off topic, but todays xkcd also has references to Hackers (1995) \$\endgroup\$
    – Sylwester
    Mar 3, 2014 at 13:46
  • 1
    \$\begingroup\$ This one also does: xkcd.com/1247 \$\endgroup\$
    – Shade
    May 16, 2014 at 16:41
3
\$\begingroup\$

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:

WallyWest SztupY 1 SztupY 2 blabla999

My own celebrity:

Garth!

Output:

Garth 75 Garth 225

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

150 300

And, if we stick with presidents:

DubbaYa 294 DubbaYa 75 DubbaYa 225

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 :P Finally; the algorithm is far from optimal.

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

Kari Byron 300 Kari Byron 75 Kari Byron 225 The Hoff 300 The Hoff 75 The Hoff 225

\$\endgroup\$
2
\$\begingroup\$

Piet

enter image description here

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:

enter image description here

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

enter image description here enter image description here

Output very large indeed:

enter image description here

\$\endgroup\$
1
\$\begingroup\$

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];
[self.view addSubview:img];

Before Danny DeVito Before After Danny DeVito After

Before Grace Kelly Before After Grace Kelly After

\$\endgroup\$
1
\$\begingroup\$

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:

Sandro Pertini
(source: ilmamilio.it)

Output with max_cubes = 25:

Sandro Pertini, Rubik'd 1

Output with max_cubes = 75:

Sandro Pertini, Rubik'd 2

Output with max_cubes = 225:

Sandro Pertini, Rubik'd 3

\$\endgroup\$
4
  • \$\begingroup\$ 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. \$\endgroup\$ Mar 1, 2014 at 20:55
  • \$\begingroup\$ @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. \$\endgroup\$
    – Oberon
    Mar 1, 2014 at 21:32
  • \$\begingroup\$ 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. \$\endgroup\$ Mar 1, 2014 at 21:54
  • \$\begingroup\$ @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! \$\endgroup\$ Mar 2, 2014 at 9:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.