Images with all colors

  • Similar to the images on allrgb.com, make images where each pixel is a unique color (no color is used twice and no color is missing).



    Give a program that generates such an image, along with a screenshot or file of the output (upload as PNG).




    • Create the image purely algorithmically.

    • Image must be 256×128 (or grid that can be screenshot and saved at 256×128)

    • Use all 15-bit colors*

    • No external input allowed (also no web queries, URLs or databases)

    • No embedded images allowed (source code which is an image is fine, e.g. Piet)

    • Dithering is allowed

    • This is not a short code contest, although it might win you votes.

    • If you're really up for a challenge, do 512×512, 2048×1024 or 4096×4096 (in increments of 3 bits).



    Scoring is by vote. Vote for the most beautiful images made by the most elegant code and/or interesting algorithm.



    Two-step algorithms, where you first generate a nice image and then fit all pixels to one of the available colors, are of course allowed, but won't win you elegance points.



    * 15-bit colors are the 32768 colors that can be made by mixing 32 reds, 32 greens, and 32 blues, all in equidistant steps and equal ranges. Example: in 24 bits images (8 bit per channel), the range per channel is 0..255 (or 0..224), so divide it up into 32 equally spaced shades.



    To be very clear, the array of image pixels should be a permutation, because all possible images have the same colors, just at different pixels locations. I'll give a trivial permutation here, which isn't beautiful at all:



    Java 7



    import java.awt.image.BufferedImage;
    import java.io.BufferedOutputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import javax.imageio.ImageIO;

    public class FifteenBitColors {
    public static void main(String[] args) {
    BufferedImage img = new BufferedImage(256, 128, BufferedImage.TYPE_INT_RGB);

    // Generate algorithmically.
    for (int i = 0; i < 32768; i++) {
    int x = i & 255;
    int y = i / 256;
    int r = i << 3 & 0xF8;
    int g = i >> 2 & 0xF8;
    int b = i >> 7 & 0xF8;
    img.setRGB(x, y, (r << 8 | g) << 8 | b);
    }

    // Save.
    try (OutputStream out = new BufferedOutputStream(new FileOutputStream("RGB15.png"))) {
    ImageIO.write(img, "png", out);
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }


    enter image description here



    Winner



    Because the 7 days are over, I'm declaring a winner



    However, by no means, think this is over. I, and all readers, always welcome more awesome designs. Don't stop creating.



    Winner: fejesjoco with 231 votes


    When you say "Dithering is allowed", what do you mean? Is this an exception to the rule "each pixel is a unique color"? If not, what are you allowing which was otherwise forbidden?

    It means you can place colors in a pattern, so when viewed with the eye, they blend into a different color. For example, see the image "clearly all RGB" on the allRGB page, and many others there.

    small tip for verifying the output: sort the pixel array and check that all values are only 1 in difference as integer

    I actually find your trivial permutation example to be quite pleasing to the eye.

    @Zom-B Man, I freakin' love this post. Thanks!

    Beautiful results/answers!

    Not a valid answer, because it uses a source image, but I enjoyed working on a version of Las grupas de Sorolla.

    I might actually work on making this an iOS app. This actually looks really cool.

    You cannot make images with all possible colors because sRGB can only represent approx. 30% of all possible colors. Also the definition of "equidistant" implicitly depends on the characterisitcs of CRT because sRGB was made to emulate CRT-monitors.

    Why I saw `creating` as `cheating`?

    I'm just browsing through some of these answers again 6 years (whoa) later. This was such a fantastic challenge. I still have one of fejesjoco's prints hanging on my wall.

  • fejesjoco

    fejesjoco Correct answer

    7 years ago

    C#



    I put a random pixel in the middle, and then start putting random pixels in a neighborhood that most resembles them. Two modes are supported: with minimum selection, only one neighboring pixel is considered at a time; with average selection, all (1..8) are averaged. Minimum selection is somewhat noisy, average selection is of course more blurred, but both look like paintings actually. After some editing, here is the current, somewhat optimized version (it even uses parallel processing!):





    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Diagnostics;
    using System.IO;

    class Program
    {
    // algorithm settings, feel free to mess with it
    const bool AVERAGE = false;
    const int NUMCOLORS = 32;
    const int WIDTH = 256;
    const int HEIGHT = 128;
    const int STARTX = 128;
    const int STARTY = 64;

    // represent a coordinate
    struct XY
    {
    public int x, y;
    public XY(int x, int y)
    {
    this.x = x;
    this.y = y;
    }
    public override int GetHashCode()
    {
    return x ^ y;
    }
    public override bool Equals(object obj)
    {
    var that = (XY)obj;
    return this.x == that.x && this.y == that.y;
    }
    }

    // gets the difference between two colors
    static int coldiff(Color c1, Color c2)
    {
    var r = c1.R - c2.R;
    var g = c1.G - c2.G;
    var b = c1.B - c2.B;
    return r * r + g * g + b * b;
    }

    // gets the neighbors (3..8) of the given coordinate
    static List<XY> getneighbors(XY xy)
    {
    var ret = new List<XY>(8);
    for (var dy = -1; dy <= 1; dy++)
    {
    if (xy.y + dy == -1 || xy.y + dy == HEIGHT)
    continue;
    for (var dx = -1; dx <= 1; dx++)
    {
    if (xy.x + dx == -1 || xy.x + dx == WIDTH)
    continue;
    ret.Add(new XY(xy.x + dx, xy.y + dy));
    }
    }
    return ret;
    }

    // calculates how well a color fits at the given coordinates
    static int calcdiff(Color[,] pixels, XY xy, Color c)
    {
    // get the diffs for each neighbor separately
    var diffs = new List<int>(8);
    foreach (var nxy in getneighbors(xy))
    {
    var nc = pixels[nxy.y, nxy.x];
    if (!nc.IsEmpty)
    diffs.Add(coldiff(nc, c));
    }

    // average or minimum selection
    if (AVERAGE)
    return (int)diffs.Average();
    else
    return diffs.Min();
    }

    static void Main(string[] args)
    {
    // create every color once and randomize the order
    var colors = new List<Color>();
    for (var r = 0; r < NUMCOLORS; r++)
    for (var g = 0; g < NUMCOLORS; g++)
    for (var b = 0; b < NUMCOLORS; b++)
    colors.Add(Color.FromArgb(r * 255 / (NUMCOLORS - 1), g * 255 / (NUMCOLORS - 1), b * 255 / (NUMCOLORS - 1)));
    var rnd = new Random();
    colors.Sort(new Comparison<Color>((c1, c2) => rnd.Next(3) - 1));

    // temporary place where we work (faster than all that many GetPixel calls)
    var pixels = new Color[HEIGHT, WIDTH];
    Trace.Assert(pixels.Length == colors.Count);

    // constantly changing list of available coordinates (empty pixels which have non-empty neighbors)
    var available = new HashSet<XY>();

    // calculate the checkpoints in advance
    var checkpoints = Enumerable.Range(1, 10).ToDictionary(i => i * colors.Count / 10 - 1, i => i - 1);

    // loop through all colors that we want to place
    for (var i = 0; i < colors.Count; i++)
    {
    if (i % 256 == 0)
    Console.WriteLine("{0:P}, queue size {1}", (double)i / WIDTH / HEIGHT, available.Count);

    XY bestxy;
    if (available.Count == 0)
    {
    // use the starting point
    bestxy = new XY(STARTX, STARTY);
    }
    else
    {
    // find the best place from the list of available coordinates
    // uses parallel processing, this is the most expensive step
    bestxy = available.AsParallel().OrderBy(xy => calcdiff(pixels, xy, colors[i])).First();
    }

    // put the pixel where it belongs
    Trace.Assert(pixels[bestxy.y, bestxy.x].IsEmpty);
    pixels[bestxy.y, bestxy.x] = colors[i];

    // adjust the available list
    available.Remove(bestxy);
    foreach (var nxy in getneighbors(bestxy))
    if (pixels[nxy.y, nxy.x].IsEmpty)
    available.Add(nxy);

    // save a checkpoint
    int chkidx;
    if (checkpoints.TryGetValue(i, out chkidx))
    {
    var img = new Bitmap(WIDTH, HEIGHT, PixelFormat.Format24bppRgb);
    for (var y = 0; y < HEIGHT; y++)
    {
    for (var x = 0; x < WIDTH; x++)
    {
    img.SetPixel(x, y, pixels[y, x]);
    }
    }
    img.Save("result" + chkidx + ".png");
    }
    }

    Trace.Assert(available.Count == 0);
    }
    }


    256x128 pixels, starting in the middle, minimum selection:






    256x128 pixels, starting in the top left corner, minimum selection:





    256x128 pixels, starting in the middle, average selection:






    Here are two 10-frame animgifs that show how minimum and average selection works (kudos to the gif format for being able to display it with 256 colors only):






    The mimimum selection mode grows with a small wavefront, like a blob, filling all pixels as it goes. In the average mode, however, when two different colored branches start growing next to each other, there will be a small black gap because nothing will be close enough to two different colors. Because of those gaps, the wavefront will be an order of magnitude larger, therefore the algorithm will be so much slower. But it's nice because it looks like a growing coral. If I would drop the average mode, it could be made a bit faster because each new color is compared to each existing pixel about 2-3 times. I see no other ways to optimize it, I think it's good enough as it is.



    And the big attraction, here's an 512x512 pixels rendering, middle start, minimum selection:





    I just can't stop playing with this! In the above code, the colors are sorted randomly. If we don't sort at all, or sort by hue ((c1, c2) => c1.GetHue().CompareTo(c2.GetHue())), we get these, respectively (both middle start and minimum selection):






    Another combination, where the coral form is kept until the end: hue ordered with average selection, with a 30-frame animgif:






    UPDATE: IT IS READY!!!



    You wanted hi-res, I wanted hi-res, you were impatient, I barely slept. Now I'm excited to announce that it's finally ready, production quality. And I am releasing it with a big bang, an awesome 1080p YouTube video! Click here for the video, let's make it viral to promote the geek style. I'm also posting stuff on my blog at http://joco.name/, there will be a technical post about all the interesting details, the optimizations, how I made the video, etc. And finally, I am sharing the source code under GPL. It's become huge so a proper hosting is the best place for this, I will not edit the above part of my answer anymore. Be sure to compile in release mode! The program scales well to many CPU cores. A 4Kx4K render requires about 2-3 GB RAM.



    I can now render huge images in 5-10 hours. I already have some 4Kx4K renders, I will post them later. The program has advanced a lot, there have been countless optimizations. I also made it user friendly so that anyone can easily use it, it has a nice command line. The program is also deterministically random, which means, you can use a random seed and it will generate the same image every time.



    Here are some big renders.



    My favorite 512:





    (source: joco.name)



    The 2048's which appear in my video:





    (source: joco.name)





    (source: joco.name)





    (source: joco.name)





    (source: joco.name)



    The first 4096 renders (TODO: they are being uploaded, and my website cannot handle the big traffic, so they are temporarily relocated):





    (source: joco.name)





    (source: joco.name)





    (source: joco.name)





    (source: joco.name)


    Now this is cool!

    Very nice :-D Now make some bigger ones!

    @squeamishossifrage: it is O(N^2) with a big constant multiplier, it takes over a minute with 32K colors, so it would need optimization before I do that. How about I do that after 10 votes :).

    @fejesjoco Start now; you'll get 10 votes; this is a really nice one! I have the same complexity issues with mine; 256x128 takes about 5 seconds but 4096x4096 takes over an hour, but optimizing is difficult because it's easier to play with stuff in unoptimized code.

    You're a true artist! :)

    OK I think I maxed it out, optimized, 512x512, animgifs, what else could I add? The 6-bit image looks just like the 5-bit one, so I'm not really interested in bigger sizes, I don't think it's worth the wait. But of course anyone can try :).

    Keep a list with all the endpoints, just like a backtracking fill algorithm

    I smell a winner! The animations are beautiful and the 512x512 really took it up a notch.

    How much for a print?

    ^ I want one too!

    I'm working on huge renders and an 1080p video. Gonna take hours or days. I hope someone will be able to create a print from a big render. Or even a t-shirt: code on one side, image on the other. Can anyone arrange that?

    whats the code for the last one? wondering about that

    @masterX244: It's the same code. Specific parameters plus a different comparator in the initial pixel sorting (the comparator will be parameterized in the next version).

    @fejesjoco what parameters? maybe you could post the parameters below for usage

    experimenting with some parameters atm to find one suitable for panorama print on 8kx2k size

    I'm rewriting the whole thing to be faster because big renders take too much time. I'm also adding more parameterization options.

    goin to abuse mah webserver as renderer for the panorama big one...

    I'm seriously considering this as a live wallpaper for my phone (at an appropriate framerate). Any objections if I throw up a github/download link if I do? Attribution would obviously be present.

    @Geobits I don't mind but I suggest to wait for the final version. Instead of linking to my SE profile, please check out my contact info on my SE profile and link to those.

    Ooooh, final version. I like the sound of that. Will do.

    rendering atm; 0,05 % on my lowend VPS and 1 % at my comp for a 8192x2048 in average mode with the hue sorting

    @fejesjoco You could submit it to threadless; they make their shirts with spot process printing so the colors should be fine, although I don't know what their size and resolution limits are. Still, they'll handle all the printing, promo, and sales. You have to get community support to get threadless to actually accept the design though. You could also just search around for printers, e.g. this guy. Dunno where you live, but maybe you can find somebody local, although threadless is nice because it handles distribution.

    @JasonC 4% atm on my render; cpu is nicely col at around 40 °C

    It gets slower as it's progressing, it may take many days. I sped it up a lot already, I barely slept. I started running some renders for the night, some of them quit with out of memory exceptions, the others have barely progressed. I'm working hard on making it even faster, please hang tight.

    yeah; i know that it may take days; but its no issue for me to let the comp run 24/7 for a while :P at least tis a nice stability test for the system

License under CC-BY-SA with attribution


Content dated before 7/24/2021 11:53 AM