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`?

  • 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

    A simple 512x512 render took minutes with the above code, took 1m20s at midnight, and after the 3rd-4th total rewrite, it's now about 20s. Let me work on it a bit more, and let's hope I can make 4Kx4K renders in a couple of hours at most.

    could you post the current versions, too?

    another issue: program doesnt support larger amounts of checkpoints (tiied 50 and above: just getting a error) @fejesjoco

    grrr; error... meant 500 checkpoints

    Needs more parallelism! I want to see those cores *burn*! :D http://i.stack.imgur.com/WmKD7.png

    @fejesjoco even that halfway optimized one would help to get that 4k one rendered cause anything is better than 2 weeks of rendertime :P

    @masterX244 and everyone: Please be patient. A halfway optimized version will simply not get a 4K render. The algorithm is brutally exponential. The 4K image can take 1000x-10000x-100000x more time than the 512K image. I expect the queue size to go up to 1 million, and it is executed millions of times. One trick can mean the difference between hours and days. I am progressing very well and have many more ideas, it shouldn't take much longer. Please wait 1 or 2 days and you will get a 4K render a lot sooner than if you start now with the unfinished version.

    Hmm... was this inspired by diffusion-limited aggregation?

    It is readyyyyyyyyyyyyyyyyyy! See my last edit!

    @Oberon I didn't know about it before. It does look like a similar concept.

    2kx1k in the one mode after hue-sorting ran thru in approx 10 minutes: `All done! It took this long: 00:10:28.3399627` `artgen 128 2048 1024 1024 512 100 9263 11111111 hue-36 0 one` was the commandline used

    btw @fejesjoco still having the seeds used for the video?

    @masterX244: I think 12345, but not sure :). Maybe it should remain a mistery :). I also added a note: you should compile in release mode, it also speeds things up a lot. I have three 4K renders ready, the fourth may be ready today or tomorrow. I will post what I have soon.

    yeah; immediately used release mode :) time to warm up my cpu, too :P

    The program now scales very well to multiple CPU's. I'm running the last 4K render on 8 cores now.

    rgb_2048_2.png is absolutely amazing!

    I added the 4Kx4K renders. Some are still uploading, but the links will point to them when they arrive. So now this project is finished. Maybe I'll dream some new ideas in the coming days.

    finally got a big render done, too.... rendered on 8192x2048 (needing wide ones for personal use) http://files.nplusc.de/public.php?service=files&;t=be95eee699d950f61cf7287b9f68e960

    All your 16M images have exactly 16,703,545 colors for some reason. That's 73671 short of a win.

    can ya tell which ones got nulled? @Zom-B

    I think I've found why your code is so slow (Order O(n^4)), and I think I can fix it, if only I could port it to Java. I'm stuck at C# api things like OrderBy, ToDictionary and AsParallel

    he already optimized :) read the latest edits after the 4k images @Zom-B :) 8kx2k took approx 6 hours with another old render runnign in paralell and other stuff running and eating cpu cycles. Currently writing a small compression utility to keep the imtermediate frames wthout hogging too much disk space up: already got a idea (using one image to track at which frame what pixel appeared)

    Nice video! The music kinda cracks me up; it's so YouTube!

    btw algorithm has one small issue which could give some more speedup if fixed (there are holes which you see as red spots in the hue-sort generated images after finishing -> those holes eat up space in the loop array and somehow get missed until the end when they are forcefilled) and thanks for keeping my CPU warm :)

    @Zom-B: NOOOOOOOOO you're destroying my life!!! Seriously? How did you count? The program does count all colors after the render and I get 16777216, no repetitions!!! And now I checked again again and always get 16777216. I even checked with GIMP's colorcube analysis, same result. And why do you instantly accuse me of losing?

    @masterX244: I know about the holes and, they are a side effect. There are just not enough similar colors to fill each branch. I doubt it can be changed. Or if you change it, it will be a different algorithm, different image. I do like it this way :).

    @Zom-B: I'm open to suggestions about speeding up. I compare each new to pixel to all pixels placed already, it has its complexity. I'm doing that with as few instructions as possible. It may be changed more drastically, I could use a colorcube for searching (except in the average sqaure case, which is the slowest btw) or port it to C... I don't think it's worth any more of my or anyone's time, it's just good enough. If I spend more time with this, that will be on new algorithms.

    Sorry for doubting you. It's Firefox that fscked up your images. I always copy/pasted images into paint shop pro and it always worked, except with your 4k images. Save As->Open in PSP and I count all colors with none missing.

    Ok ZomB... It's just that I lived my life for this thing in the last 3 days, and you scared the hell out of me and I got very nervous. I'm alright now. I think I will submit a Firefox bugreport for that.

    @masterX244 can I ask what you will do with these images? My wife wants to hang one on our wall :)

    @fejesjoco some on my wall, too but others used as paper to fold nice looking boxes :) btw CPU still on full load :P and wide format fitted better on my wall, thats why i make those

    @fejesjoco currently trying to port the simple version of your algo to java as part for my respone to another question but somehow the code says GAH! and doesnt work

    Give him a medal :) awesome and neat.

    @fejesjoco : seems that the big renders went 404 on your site

    Yes, my site is struggling with the huge traffic, the video is going viral. I uploaded the big images to allrgb.com and my google drive (links can be found on my blog). I will re-upload them later when the traffic gets lower.

    OK guys I fixed the links to point to my Google drive. I also added my fourth 4Kx4K render. It's similar to the 2nd, but look more closely!

    Amazing. Well done. + 1

    This really calls for a GPU implementation... It could work significantly faster :)

    I got some tips here and elsewhere to submit my design to Threadless. So I did and I am waiting for approval. As it turns out, it's not as easy as submitting a rendered PNG, you have to make a complete design, survive several rounds of approval, and then get community votes. Of course I will send you a link when it's time to vote, but I doubt I have any chance next to those real pro designers there. Can anybody find a designer who can actually do something with these images? And not just mine of course, there are many awesome answers here.

    The program is now featured on newscientist.com! I just want to thank you all guys, especially @Zom-B, for starting this thing and voting and giving ideas. It's also your success, codegolf's success, all geeks success :)

    by the way: somehow those weird black "canyons" are generated only after the change; posting a comparison of a 8kx2k render in old and new algorithm in the next few days when it finished rendering

    How awesome. Congratulations again, @fejesjoco

    The allRGB website is now having capacity problems, ROFL

    Some of you mentioned you would like a print. You can now get it here, I will add some more later (promo link with free shipping): http://society6.com/fejesjoco?promo=4552f3

    @MarkJeronimus They got CodeGolfed! It's great to see so many submissions from this thread on there!

    @fejesjoco Got me an extra large one. Man you're really working the fame here; nice job! :D I'll keep an eye out for t-shirt printers too, but short-run high-quality photorealistic prints are kind of a specialty job (either the shop needs special equipment, or a screen printer has to be skilled with process prints).

    You are in news !! http://www.newscientist.com/article/dn25167-computer-paints-rainbow-smoke-with-17-million-colours.html#.UxjKznYgp2N. I created an account in this site just to tell you this :) I had seen this answer in this site couple of days ago and surprized to see it in news !!

    by the way finished a 8kx2k render with the program out of the post aka the unoptimized version; took me 6 days to finish.... (wanted to see the differences added by the optimisations :)

    You also made Gizmodo! I don't think I've ever seen the results of an of the CodeGolf challenges get this kinda of feeback. Really really really well done.

    @fejesjoco Print arrived today; it's gorgeous -- society6 did a beautiful, high-quality job. Do you get decent kick-backs for sales with them?

    In case anyone's still reading, here's an app for Android that I'm working on: https://play.google.com/store/apps/details?id=name.joco.rainbowsmoke.demo

    Whoa, this is great!

    I have no words, it's just art.... Well done!

    The output from this looks scarily similar to an image-generation program I've been working on for a while: https://github.com/g-rocket/Starburst/ (well, some of them do). For example, see this: https://dl.dropboxusercontent.com/u/29197095/example.png (generated with properties set to [-.2,-.2,-.2,0,0,2] and seed properties set to [3,1,0,3]).

    These look like beautiful rainbow coral reefs.

    For anyone who is having trouble deciding on a `NUMCOLORS` to use when they change the dimensions.. use `NUMCOLORS = (int)Math.Ceiling(Math.Pow(HEIGHT* WIDTH, (1.0 / 3.0)));`

    After seeing this, I wrote a script to generate random images. They happen to look very similar to these.

    About to make this into an iOS app so people can generate their own images. :D

    Umm the huge render links are dead.

    This is beautiful.

    The last 4 links point to a Google Drive location which no longer exists.

    @fejesjoco it would appear that the source code hosted on google code is not available? I am getting an `401: Anonymous users does not have storage.objects.get access to object google-code-archive/v2/code.google.com/joco-tools/project.json.` or `The project joco-tools was not found.` error every time i attempt to access it :( would it be possible to share the source code via GitHub or otherwise?

    What happened to the alternative code where the previews on the website got posted after the initial version?

    @TaylorScott https://nplusc.de/rgbgen.zip Had the source still floating around in a corner of my harddisks

    20K CPU-hours in on a Ryzen processor on rendering one of those corals. 50% according to output but my estimate is 30%. Rendering it with 10K steps

    I immediately thought this was a pretty neat idea when I saw it a couple years ago, but was kind of shocked at how slow it is. I have since implemented it in Python (renders in an hour or two), Java (renders in about 2-3 minutes) and most recently C++ (renders in about 20-25 seconds), all single threaded (w/ fully shuffled colors, the fastest). Most variants use CIELAB colorspace to look extra good; strict color orderings (non-shuffled colors, and similar) produce extremely wild outputs, sometimes deterministically. The variety is endless; I could be coaxed into posting examples and a repo.

    @Mumbleskates make repo please!

    @Mumbleskates thanks

    @Mumbleskates Just FYI, my latest version runs in a couple of seconds. That's how it can also run in the Android app.

    @fejesjoco Nice. I'm assuming the approach is similar, if that's the case; what size is it rendering? The only reason the one I made takes so long (30-40 minutes for some rendering modes, full 24 bit) is because it seeks exact best answers. Introducing stochastic inaccuracy could greatly speed it up, but I've found that for some of the coolest patterns this actually makes the end result less interesting.

    The joco.name and source code links appear to be dead. Can these be updated?

    rainbowsmoke.hu still works

  • Processing

    Update! 4096x4096 images!

    I've merged my second post into this one by combining the two programs together.

    A full collection of selected images can be found here, on Dropbox. (Note: DropBox can't generate previews for the 4096x4096 images; just click them then click "Download").

    If you only look at one look at this one (tileable)! Here it is scaled down (and many more below), original 2048x1024:

    enter image description here

    This program works by walking paths from randomly selected points in the color cube, then drawing them into randomly selected paths in the image. There are a lot of possibilities. Configurable options are:

    • Maximum length of color cube path.
    • Maximum step to take through color cube (larger values cause larger variance but minimize the number of small paths towards the end when things get tight).
    • Tiling the image.
    • There are currently two image path modes:
      • Mode 1 (the mode of this original post): Finds a block of unused pixels in the image and renders to that block. Blocks can be either randomly located, or ordered from left to right.
      • Mode 2 (the mode of my second post that I merged into this one): Picks a random start point in the image and walks along a path through unused pixels; can walk around used pixels. Options for this mode:
        • Set of directions to walk in (orthogonal, diagonal, or both).
        • Whether or not to change the direction (currently clockwise but code is flexible) after each step, or to only change direction upon encountering an occupied pixel..
        • Option to shuffle order of direction changes (instead of clockwise).

    It works for all sizes up to 4096x4096.

    The complete Processing sketch can be found here: Tracer.zip

    I've pasted all the files in the same code block below just to save space (even all in one file, it is still a valid sketch). If you want to use one of the presets, change the index in the gPreset assignment. If you run this in Processing you can press r while it is running to generate a new image.

    • Update 1: Optimized code to track first unused color/pixel and not search over known-used pixels; reduced 2048x1024 generation time from 10-30 minutes down to about 15 seconds, and 4096x4096 from 1-3 hours to about 1 minute. Drop box source and source below updated.
    • Update 2: Fixed bug that was preventing 4096x4096 images from being generated.
    final int BITS = 5; // Set to 5, 6, 7, or 8!
    
    // Preset (String name, int colorBits, int maxCubePath, int maxCubeStep, int imageMode, int imageOpts)
    final Preset[] PRESETS = new Preset[] {
      // 0
      new Preset("flowers",      BITS, 8*32*32, 2, ImageRect.MODE2, ImageRect.ALL_CW | ImageRect.CHANGE_DIRS),
      new Preset("diamonds",     BITS, 2*32*32, 2, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.CHANGE_DIRS),
      new Preset("diamondtile",  BITS, 2*32*32, 2, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.CHANGE_DIRS | ImageRect.WRAP),
      new Preset("shards",       BITS, 2*32*32, 2, ImageRect.MODE2, ImageRect.ALL_CW | ImageRect.CHANGE_DIRS | ImageRect.SHUFFLE_DIRS),
      new Preset("bigdiamonds",  BITS,  100000, 6, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.CHANGE_DIRS),
      // 5
      new Preset("bigtile",      BITS,  100000, 6, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.CHANGE_DIRS | ImageRect.WRAP),
      new Preset("boxes",        BITS,   32*32, 2, ImageRect.MODE2, ImageRect.ORTHO_CW),
      new Preset("giftwrap",     BITS,   32*32, 2, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.WRAP),
      new Preset("diagover",     BITS,   32*32, 2, ImageRect.MODE2, ImageRect.DIAG_CW),
      new Preset("boxfade",      BITS,   32*32, 2, ImageRect.MODE2, ImageRect.DIAG_CW | ImageRect.CHANGE_DIRS),
      // 10
      new Preset("randlimit",    BITS,     512, 2, ImageRect.MODE1, ImageRect.RANDOM_BLOCKS),
      new Preset("ordlimit",     BITS,      64, 2, ImageRect.MODE1, 0),
      new Preset("randtile",     BITS,    2048, 3, ImageRect.MODE1, ImageRect.RANDOM_BLOCKS | ImageRect.WRAP),
      new Preset("randnolimit",  BITS, 1000000, 1, ImageRect.MODE1, ImageRect.RANDOM_BLOCKS),
      new Preset("ordnolimit",   BITS, 1000000, 1, ImageRect.MODE1, 0)
    };
    
    
    PGraphics gFrameBuffer;
    Preset gPreset = PRESETS[2];
    
    void generate () {
      ColorCube cube = gPreset.createCube();
      ImageRect image = gPreset.createImage();
      gFrameBuffer = createGraphics(gPreset.getWidth(), gPreset.getHeight(), JAVA2D);
      gFrameBuffer.noSmooth();
      gFrameBuffer.beginDraw();
      while (!cube.isExhausted())
        image.drawPath(cube.nextPath(), gFrameBuffer);
      gFrameBuffer.endDraw();
      if (gPreset.getName() != null)
        gFrameBuffer.save(gPreset.getName() + "_" + gPreset.getCubeSize() + ".png");
      //image.verifyExhausted();
      //cube.verifyExhausted();
    }
    
    void setup () {
      size(gPreset.getDisplayWidth(), gPreset.getDisplayHeight());
      noSmooth();
      generate();
    }
    
    void keyPressed () {
      if (key == 'r' || key == 'R')
        generate();
    }
    
    boolean autogen = false;
    int autop = 0;
    int autob = 5;
    
    void draw () {
      if (autogen) {
        gPreset = new Preset(PRESETS[autop], autob);
        generate();
        if ((++ autop) >= PRESETS.length) {
          autop = 0;
          if ((++ autob) > 8)
            autogen = false;
        }
      }
      if (gPreset.isWrapped()) {
        int hw = width/2;
        int hh = height/2;
        image(gFrameBuffer, 0, 0, hw, hh);
        image(gFrameBuffer, hw, 0, hw, hh);
        image(gFrameBuffer, 0, hh, hw, hh);
        image(gFrameBuffer, hw, hh, hw, hh);
      } else {
        image(gFrameBuffer, 0, 0, width, height);
      }
    }
    
    static class ColorStep {
      final int r, g, b;
      ColorStep (int rr, int gg, int bb) { r=rr; g=gg; b=bb; }
    }
    
    class ColorCube {
    
      final boolean[] used;
      final int size; 
      final int maxPathLength;
      final ArrayList<ColorStep> allowedSteps = new ArrayList<ColorStep>();
    
      int remaining;
      int pathr = -1, pathg, pathb;
      int firstUnused = 0;
    
      ColorCube (int size, int maxPathLength, int maxStep) {
        this.used = new boolean[size*size*size];
        this.remaining = size * size * size;
        this.size = size;
        this.maxPathLength = maxPathLength;
        for (int r = -maxStep; r <= maxStep; ++ r)
          for (int g = -maxStep; g <= maxStep; ++ g)
            for (int b = -maxStep; b <= maxStep; ++ b)
              if (r != 0 && g != 0 && b != 0)
                allowedSteps.add(new ColorStep(r, g, b));
      }
    
      boolean isExhausted () {
        println(remaining);
        return remaining <= 0;
      }
    
      boolean isUsed (int r, int g, int b) {
        if (r < 0 || r >= size || g < 0 || g >= size || b < 0 || b >= size)
          return true;
        else
          return used[(r*size+g)*size+b];
      }
    
      void setUsed (int r, int g, int b) {
        used[(r*size+g)*size+b] = true;
      }
    
      int nextColor () {
    
        if (pathr == -1) { // Need to start a new path.
    
          // Limit to 50 attempts at random picks; things get tight near end.
          for (int n = 0; n < 50 && pathr == -1; ++ n) {
            int r = (int)random(size);
            int g = (int)random(size);
            int b = (int)random(size);
            if (!isUsed(r, g, b)) {
              pathr = r;
              pathg = g;
              pathb = b;
            }
          }
          // If we didn't find one randomly, just search for one.
          if (pathr == -1) {
            final int sizesq = size*size;
            final int sizemask = size - 1;
            for (int rgb = firstUnused; rgb < size*size*size; ++ rgb) {
              pathr = (rgb/sizesq)&sizemask;//(rgb >> 10) & 31;
              pathg = (rgb/size)&sizemask;//(rgb >> 5) & 31;
              pathb = rgb&sizemask;//rgb & 31;
              if (!used[rgb]) {
                firstUnused = rgb;
                break;
              }
            }
          }
    
          assert(pathr != -1);
    
        } else { // Continue moving on existing path.
    
          // Find valid next path steps.
          ArrayList<ColorStep> possibleSteps = new ArrayList<ColorStep>();
          for (ColorStep step:allowedSteps)
            if (!isUsed(pathr+step.r, pathg+step.g, pathb+step.b))
              possibleSteps.add(step);
    
          // If there are none end this path.
          if (possibleSteps.isEmpty()) {
            pathr = -1;
            return -1;
          }
    
          // Otherwise pick a random step and move there.
          ColorStep s = possibleSteps.get((int)random(possibleSteps.size()));
          pathr += s.r;
          pathg += s.g;
          pathb += s.b;
    
        }
    
        setUsed(pathr, pathg, pathb);  
        return 0x00FFFFFF & color(pathr * (256/size), pathg * (256/size), pathb * (256/size));
    
      } 
    
      ArrayList<Integer> nextPath () {
    
        ArrayList<Integer> path = new ArrayList<Integer>(); 
        int rgb;
    
        while ((rgb = nextColor()) != -1) {
          path.add(0xFF000000 | rgb);
          if (path.size() >= maxPathLength) {
            pathr = -1;
            break;
          }
        }
    
        remaining -= path.size();
    
        //assert(!path.isEmpty());
        if (path.isEmpty()) {
          println("ERROR: empty path.");
          verifyExhausted();
        }
        return path;
    
      }
    
      void verifyExhausted () {
        final int sizesq = size*size;
        final int sizemask = size - 1;
        for (int rgb = 0; rgb < size*size*size; ++ rgb) {
          if (!used[rgb]) {
            int r = (rgb/sizesq)&sizemask;
            int g = (rgb/size)&sizemask;
            int b = rgb&sizemask;
            println("UNUSED COLOR: " + r + " " + g + " " + b);
          }
        }
        if (remaining != 0)
          println("REMAINING COLOR COUNT IS OFF: " + remaining);
      }
    
    }
    
    
    static class ImageStep {
      final int x;
      final int y;
      ImageStep (int xx, int yy) { x=xx; y=yy; }
    }
    
    static int nmod (int a, int b) {
      return (a % b + b) % b;
    }
    
    class ImageRect {
    
      // for mode 1:
      //   one of ORTHO_CW, DIAG_CW, ALL_CW
      //   or'd with flags CHANGE_DIRS
      static final int ORTHO_CW = 0;
      static final int DIAG_CW = 1;
      static final int ALL_CW = 2;
      static final int DIR_MASK = 0x03;
      static final int CHANGE_DIRS = (1<<5);
      static final int SHUFFLE_DIRS = (1<<6);
    
      // for mode 2:
      static final int RANDOM_BLOCKS = (1<<0);
    
      // for both modes:
      static final int WRAP = (1<<16);
    
      static final int MODE1 = 0;
      static final int MODE2 = 1;
    
      final boolean[] used;
      final int width;
      final int height;
      final boolean changeDir;
      final int drawMode;
      final boolean randomBlocks;
      final boolean wrap;
      final ArrayList<ImageStep> allowedSteps = new ArrayList<ImageStep>();
    
      // X/Y are tracked instead of index to preserve original unoptimized mode 1 behavior
      // which does column-major searches instead of row-major.
      int firstUnusedX = 0;
      int firstUnusedY = 0;
    
      ImageRect (int width, int height, int drawMode, int drawOpts) {
        boolean myRandomBlocks = false, myChangeDir = false;
        this.used = new boolean[width*height];
        this.width = width;
        this.height = height;
        this.drawMode = drawMode;
        this.wrap = (drawOpts & WRAP) != 0;
        if (drawMode == MODE1) {
          myRandomBlocks = (drawOpts & RANDOM_BLOCKS) != 0;
        } else if (drawMode == MODE2) {
          myChangeDir = (drawOpts & CHANGE_DIRS) != 0;
          switch (drawOpts & DIR_MASK) {
          case ORTHO_CW:
            allowedSteps.add(new ImageStep(1, 0));
            allowedSteps.add(new ImageStep(0, -1));
            allowedSteps.add(new ImageStep(-1, 0));
            allowedSteps.add(new ImageStep(0, 1));
            break;
          case DIAG_CW:
            allowedSteps.add(new ImageStep(1, -1));
            allowedSteps.add(new ImageStep(-1, -1));
            allowedSteps.add(new ImageStep(-1, 1));
            allowedSteps.add(new ImageStep(1, 1));
            break;
          case ALL_CW:
            allowedSteps.add(new ImageStep(1, 0));
            allowedSteps.add(new ImageStep(1, -1));
            allowedSteps.add(new ImageStep(0, -1));
            allowedSteps.add(new ImageStep(-1, -1));
            allowedSteps.add(new ImageStep(-1, 0));
            allowedSteps.add(new ImageStep(-1, 1));
            allowedSteps.add(new ImageStep(0, 1));
            allowedSteps.add(new ImageStep(1, 1));
            break;
          }
          if ((drawOpts & SHUFFLE_DIRS) != 0)
            java.util.Collections.shuffle(allowedSteps);
        }
        this.randomBlocks = myRandomBlocks;
        this.changeDir = myChangeDir;
      }
    
      boolean isUsed (int x, int y) {
        if (wrap) {
          x = nmod(x, width);
          y = nmod(y, height);
        }
        if (x < 0 || x >= width || y < 0 || y >= height)
          return true;
        else
          return used[y*width+x];
      }
    
      boolean isUsed (int x, int y, ImageStep d) {
        return isUsed(x + d.x, y + d.y);
      }
    
      void setUsed (int x, int y) {
        if (wrap) {
          x = nmod(x, width);
          y = nmod(y, height);
        }
        used[y*width+x] = true;
      }
    
      boolean isBlockFree (int x, int y, int w, int h) {
        for (int yy = y; yy < y + h; ++ yy)
          for (int xx = x; xx < x + w; ++ xx)
            if (isUsed(xx, yy))
              return false;
        return true;
      }
    
      void drawPath (ArrayList<Integer> path, PGraphics buffer) {
        if (drawMode == MODE1)
          drawPath1(path, buffer);
        else if (drawMode == MODE2)
          drawPath2(path, buffer);
      }
    
      void drawPath1 (ArrayList<Integer> path, PGraphics buffer) {
    
        int w = (int)(sqrt(path.size()) + 0.5);
        if (w < 1) w = 1; else if (w > width) w = width;
        int h = (path.size() + w - 1) / w; 
        int x = -1, y = -1;
    
        int woff = wrap ? 0 : (1 - w);
        int hoff = wrap ? 0 : (1 - h);
    
        // Try up to 50 times to find a random location for block.
        if (randomBlocks) {
          for (int n = 0; n < 50 && x == -1; ++ n) {
            int xx = (int)random(width + woff);
            int yy = (int)random(height + hoff);
            if (isBlockFree(xx, yy, w, h)) {
              x = xx;
              y = yy;
            }
          }
        }
    
        // If random choice failed just search for one.
        int starty = firstUnusedY;
        for (int xx = firstUnusedX; xx < width + woff && x == -1; ++ xx) {
          for (int yy = starty; yy < height + hoff && x == -1; ++ yy) {
            if (isBlockFree(xx, yy, w, h)) {
              firstUnusedX = x = xx;
              firstUnusedY = y = yy;
            }  
          }
          starty = 0;
        }
    
        if (x != -1) {
          for (int xx = x, pathn = 0; xx < x + w && pathn < path.size(); ++ xx)
            for (int yy = y; yy < y + h && pathn < path.size(); ++ yy, ++ pathn) {
              buffer.set(nmod(xx, width), nmod(yy, height), path.get(pathn));
              setUsed(xx, yy);
            }
        } else {
          for (int yy = 0, pathn = 0; yy < height && pathn < path.size(); ++ yy)
            for (int xx = 0; xx < width && pathn < path.size(); ++ xx)
              if (!isUsed(xx, yy)) {
                buffer.set(nmod(xx, width), nmod(yy, height), path.get(pathn));
                setUsed(xx, yy);
                ++ pathn;
              }
        }
    
      }
    
      void drawPath2 (ArrayList<Integer> path, PGraphics buffer) {
    
        int pathn = 0;
    
        while (pathn < path.size()) {
    
          int x = -1, y = -1;
    
          // pick a random location in the image (try up to 100 times before falling back on search)
    
          for (int n = 0; n < 100 && x == -1; ++ n) {
            int xx = (int)random(width);
            int yy = (int)random(height);
            if (!isUsed(xx, yy)) {
              x = xx;
              y = yy;
            }
          }  
    
          // original:
          //for (int yy = 0; yy < height && x == -1; ++ yy)
          //  for (int xx = 0; xx < width && x == -1; ++ xx)
          //    if (!isUsed(xx, yy)) {
          //      x = xx;
          //      y = yy;
          //    }
          // optimized:
          if (x == -1) {
            for (int n = firstUnusedY * width + firstUnusedX; n < used.length; ++ n) {
              if (!used[n]) {
                firstUnusedX = x = (n % width);
                firstUnusedY = y = (n / width);
                break;
              }     
            }
          }
    
          // start drawing
    
          int dir = 0;
    
          while (pathn < path.size()) {
    
            buffer.set(nmod(x, width), nmod(y, height), path.get(pathn ++));
            setUsed(x, y);
    
            int diro;
            for (diro = 0; diro < allowedSteps.size(); ++ diro) {
              int diri = (dir + diro) % allowedSteps.size();
              ImageStep step = allowedSteps.get(diri);
              if (!isUsed(x, y, step)) {
                dir = diri;
                x += step.x;
                y += step.y;
                break;
              }
            }
    
            if (diro == allowedSteps.size())
              break;
    
            if (changeDir) 
              ++ dir;
    
          }    
    
        }
    
      }
    
      void verifyExhausted () {
        for (int n = 0; n < used.length; ++ n)
          if (!used[n])
            println("UNUSED IMAGE PIXEL: " + (n%width) + " " + (n/width));
      }
    
    }
    
    
    class Preset {
    
      final String name;
      final int cubeSize;
      final int maxCubePath;
      final int maxCubeStep;
      final int imageWidth;
      final int imageHeight;
      final int imageMode;
      final int imageOpts;
      final int displayScale;
    
      Preset (Preset p, int colorBits) {
        this(p.name, colorBits, p.maxCubePath, p.maxCubeStep, p.imageMode, p.imageOpts);
      }
    
      Preset (String name, int colorBits, int maxCubePath, int maxCubeStep, int imageMode, int imageOpts) {
        final int csize[] = new int[]{ 32, 64, 128, 256 };
        final int iwidth[] = new int[]{ 256, 512, 2048, 4096 };
        final int iheight[] = new int[]{ 128, 512, 1024, 4096 };
        final int dscale[] = new int[]{ 2, 1, 1, 1 };
        this.name = name; 
        this.cubeSize = csize[colorBits - 5];
        this.maxCubePath = maxCubePath;
        this.maxCubeStep = maxCubeStep;
        this.imageWidth = iwidth[colorBits - 5];
        this.imageHeight = iheight[colorBits - 5];
        this.imageMode = imageMode;
        this.imageOpts = imageOpts;
        this.displayScale = dscale[colorBits - 5];
      }
    
      ColorCube createCube () {
        return new ColorCube(cubeSize, maxCubePath, maxCubeStep);
      }
    
      ImageRect createImage () {
        return new ImageRect(imageWidth, imageHeight, imageMode, imageOpts);
      }
    
      int getWidth () {
        return imageWidth;
      }
    
      int getHeight () {
        return imageHeight;
      }
    
      int getDisplayWidth () {
        return imageWidth * displayScale * (isWrapped() ? 2 : 1);
      }
    
      int getDisplayHeight () {
        return imageHeight * displayScale * (isWrapped() ? 2 : 1);
      }
    
      String getName () {
        return name;
      }
    
      int getCubeSize () {
        return cubeSize;
      }
    
      boolean isWrapped () {
        return (imageOpts & ImageRect.WRAP) != 0;
      }
    
    }
    

    Here is a full set of 256x128 images that I like:

    Mode 1:

    My favorite from original set (max_path_length=512, path_step=2, random, displayed 2x, link 256x128):

    enter image description here

    Others (left two ordered, right two random, top two path length limited, bottom two unlimitted):

    ordlimit randlimit ordnolimit randnolimit

    This one can be tiled:

    randtile

    Mode 2:

    diamonds flowers boxfade diagover bigdiamonds boxes2 shards

    These ones can be tiled:

    bigtile diamondtile giftwrap

    512x512 selections:

    Tileable diamonds, my favorite from mode 2; you can see in this one how the paths walk around existing objects:

    enter image description here

    Larger path step and max path length, tileable:

    enter image description here

    Random mode 1, tileable:

    enter image description here

    More selections:

    enter image description here enter image description here enter image description here

    All of the 512x512 renderings can be found in the dropbox folder (*_64.png).

    2048x1024 and 4096x4096:

    These are too large to embed and all the image hosts I found drop them down to 1600x1200. I'm currently rendering a set of 4096x4096 images so more will be available soon. Instead of including all the links here, just go check them out in the dropbox folder (*_128.png and *_256.png, note: the 4096x4096 ones are too big for the dropbox previewer, just click "download"). Here are some of my favorites, though:

    2048x1024 big tileable diamonds (same one I linked to at start of this post)

    2048x1024 diamonds (I love this one!), scaled down:

    enter image description here

    4096x4096 big tileable diamonds (Finally! Click 'download' in Dropbox link; it's too large for their previewer), scaled way down:

    4096x4096 big tileable diamonds

    4096x4096 random mode 1: enter image description here

    4096x4096 another cool one

    Update: The 2048x1024 preset image set is finished and in the dropbox. The 4096x4096 set should be done within the hour.

    There's tons of good ones, I'm having a really hard time picking which ones to post, so please check out the folder link!

    The first image is my favorite; it looks like things flying through the air with a ground and sky horizon behind it. Well, that and about 10 hits of acid.

    It reminds me of close-up views of some minerals.

    Awesome! That's the spirit I've been missing until now.

    Some of them look like those magnified processor die photos.

    Try treating the canvas as a toroidal topology (borderless, tiling)

    The 2048x1024 diamonds one is now my laptop's background image

    @CarterPape Yes!! Generating something that somebody else uses as a background is kinda the best compliment ever. *Fist pump!* :D Hopefully I'll have some 4096x4096 ones up soon; I just spent about 2 hours waiting for one to render only to have an assertion failure on the *very last pixel*. :(

    @Zom-B Done and done; check out the 4th image in this post. I have rewritten the code to be much cleaner and to combine both this post and my other post into the same program; so I am going to work on merging this into my other post then deleting this. Although, to be honest, fejesjoco is going to kick our butts no matter what I do, lol.

    I've merged my second post into this one.

    Not part of the contest, but I thought this was kinda cool; I applied a big gaussian blur and auto contrast enhance to one of the random mode 1 pics in photoshop and it made kind of a nice desktop background-y sort of thing.

    whoa, these are cool pictures!

    @Zom-B Funny I was just thinking about rotating that one.

    Reminds me of Gustav Klimt textures.

    I'm having a really hard time generating 4096x4096 textures; for some reason `ColorCube.nextPath()` is returning an empty path for the very last pixel, but only in 4096x4096 mode. This shouldn't happen because the cube and the image have exactly the same cell count. I suspect a subtle integer overflow somewhere. I will update here when I can actually render the large size. The 2048x1024 set is complete, though.

    @JasonC oucherz; stupid bug....

    Update: An easy optimization in the code reduced 2048x1024 generation times from 10-30 minutes down to about 15 seconds. I should be able to solve the large image bug now too. I've updated the source on Drop Box as well as above. Enjoy!

    Update: Bug fixed and it works with 4096x4096 images now; will post images soon. @masterX244: I forgot Processing's `color(r,g,b)` function returns `0xAARRGGBB` with alpha set to 255. I used -1 to signal the end of a color path but for the color (255, 255, 255), the result was the same as -1 (it only failed for 4096x4096 because that's the only one with 255,255,255 in the palette). Kinda under the radar. It just so happened that more often than not, 255,255,255 was the last color picked. I solved it by masking out the alpha component before returning the rgb value.

    @Zom-B Finally got some 4096x4096 images up. Here's a tileable diamond one.

    Did you know you can hotlink images in Dropbox? Just copy the download URL, remove the `dl=1` and the `token_hash=` part and make a link to your image like this: `![Alt text of my small preview image](https://dl.dropbox.com/linktoyourfullsiz‌eimage.png)`. Another tip: you can compress your images (I get good results with TruePNG (Download)). I was able to save 28.1% of the file size on this image.

    @user2428118 Oh sweet! Thanks! I had tried hotlinking just with the "link" button but it didn't work so I figured it wasn't possible. I'll see if I can compress the images, too. ZIP and RAR weren't able to do much with them, so I didn't try anything else, I'll try TruePNG.

    The Klimt-Gogh algorithm.

  • Python w/ PIL

    This is based on a Newtonian Fractal, specifically for z → z5 - 1. Because there are five roots, and thus five convergence points, the available color space is split into five regions, based on Hue. The individual points are sorted first by number of iterations required to reach their convergence point, and then by distance to that point, with earlier values being assigned a more luminous color.

    Update: 4096x4096 big renders, hosted on allrgb.com.

    Original (33.7 MB)

    A close-up of the very center (actual size):

    A different vantage point using these values:

    xstart = 0
    ystart = 0
    
    xd = 1 / dim[0]
    yd = 1 / dim[1]
    

    Original (32.2 MB)

    And another using these:

    xstart = 0.5
    ystart = 0.5
    
    xd = 0.001 / dim[0]
    yd = 0.001 / dim[1]
    

    Original (27.2 MB)


    Animation

    By request, I've compiled a zoom animation.

    Focal Point: (0.50051, -0.50051)
    Zoom factor: 21/5

    The focal point is a slightly odd value, because I didn't want to zoom in on a black dot. The zoom factor is chosen such that it doubles every 5 frames.

    A 32x32 teaser:

    A 256x256 version can be seen here:
    http://www.pictureshack.org/images/66172_frac.gif (5.4MB)

    There may be points that mathematically zoom in "onto themselves," which would allow for an infinite animation. If I can identify any, I'll add them here.


    Source

    from __future__ import division
    from PIL import Image, ImageDraw
    from cmath import phase
    from sys import maxint
    
    dim  = (4096, 4096)
    bits = 8
    
    def RGBtoHSV(R, G, B):
      R /= 255
      G /= 255
      B /= 255
    
      cmin = min(R, G, B)
      cmax = max(R, G, B)
      dmax = cmax - cmin
    
      V = cmax
    
      if dmax == 0:
        H = 0
        S = 0
    
      else:
        S = dmax/cmax
    
        dR = ((cmax - R)/6 + dmax/2)/dmax
        dG = ((cmax - G)/6 + dmax/2)/dmax
        dB = ((cmax - B)/6 + dmax/2)/dmax
    
        if   R == cmax: H = (dB - dG)%1
        elif G == cmax: H = (1/3 + dR - dB)%1
        elif B == cmax: H = (2/3 + dG - dR)%1
    
      return (H, S, V)
    
    cmax = (1<<bits)-1
    cfac = 255/cmax
    
    img  = Image.new('RGB', dim)
    draw = ImageDraw.Draw(img)
    
    xstart = -2
    ystart = -2
    
    xd = 4 / dim[0]
    yd = 4 / dim[1]
    
    tol = 1e-12
    
    a = [[], [], [], [], []]
    
    for x in range(dim[0]):
      print x, "\r",
      for y in range(dim[1]):
        z = d = complex(xstart + x*xd, ystart + y*yd)
        c = 0
        l = 1
        while abs(l-z) > tol and abs(z) > tol:
          l = z
          z -= (z**5-1)/(5*z**4)
          c += 1
        if z == 0: c = maxint
        p = int(phase(z))
    
        a[p] += (c,abs(d-z), x, y),
    
    for i in range(5):
      a[i].sort(reverse = False)
    
    pnum = [len(a[i]) for i in range(5)]
    ptot = dim[0]*dim[1]
    
    bounds = []
    lbound = 0
    for i in range(4):
      nbound = lbound + pnum[i]/ptot
      bounds += nbound,
      lbound = nbound
    
    t = [[], [], [], [], []]
    for i in range(ptot-1, -1, -1):
      r = (i>>bits*2)*cfac
      g = (cmax&i>>bits)*cfac
      b = (cmax&i)*cfac
      (h, s, v) = RGBtoHSV(r, g, b)
      h = (h+0.1)%1
      if   h < bounds[0] and len(t[0]) < pnum[0]: p=0
      elif h < bounds[1] and len(t[1]) < pnum[1]: p=1
      elif h < bounds[2] and len(t[2]) < pnum[2]: p=2
      elif h < bounds[3] and len(t[3]) < pnum[3]: p=3
      else: p=4
      t[p] += (int(r), int(g), int(b)),
    
    for i in range(5):
      t[i].sort(key = lambda c: c[0]*2126 + c[1]*7152 + c[2]*722, reverse = True)
    
    r = [0, 0, 0, 0, 0]
    for p in range(5):
      for c,d,x,y in a[p]:
        draw.point((x,y), t[p][r[p]])
        r[p] += 1
    
    img.save("out.png")
    

    Finally a fractal :) Love those. Also, that green at 144 degrees is my favorite color (as opposed to pure green at 120 degrees which is just boring).

    I'm glad you like it :) Pure coincidence about the green, though (360 * 2/5). A version I've made for myself (without the allrgb restriction) has much nicer output: http://i.stack.imgur.com/4H3m1.png

    I dunno, I actually kind of like the AllRGB versions better; the need to use the full luminance space nicely emphasizes the gradients.

    @Zom-B My answer also has a fractal: The Sierpinski Triangle. :-) (and it was posted earlier).

    +1 Finally some good fractals! The last one is my personal favorite. You should make a video zooming in! (@Quincunx: Saw yours too; it had my vote from day 1!)

    @JasonC I've added an animation ;)

    @primo Nice!! These are so cool! I love how the hues change as it zooms.

    Come join the CodeGolf club on allrgb.com, lol. P.S. Nice avatar.

    @primo I know I'm late, but I just wanted to say these images are spectacular.

    @JasonC done... two and half years later. The render requires more than 4GB of RAM, which I didn't have at the time.

    "Anything worth doing is worth overdoing" :D

    Your pictureshack.us link seems broken, can be fixed by linking to pictureshack.org.

    @JonathanFrech thanks :)

  • I got this idea from user fejesjoco's algorithm and wanted to play a bit, so I started to write my own algorithm from scratch.

    I'm posting this because I feel that if I can make something better* than the best out of you guys, I don't think this challenge is finished yet. To compare, there are some stunning designs on allRGB that I consider way beyond the level reached here and I have no idea how they did it.

    *) will still be decided by votes

    This algorithm:

    1. Start with a (few) seed(s), with colors as close as possible to black.
    2. Keep a list of all pixels that are unvisited and 8-connected to a visited point.
    3. Select a random** point from that list
    4. Calculate the average color of all calculated pixels [Edit ...in a 9x9 square using a Gaussian kernel] 8-connected to it (this is the reason why it looks so smooth) If none are found, take black.
    5. in a 3x3x3 cube around this color, search for an unused color.
      • When multple colors are found, take the darkest one.
      • When multple equally dark colors are found, take a random one out of those.
      • When nothing is found, update the search range to 5x5x5, 7x7x7, etc. Repeat from 5.
    6. Plot pixel, update list and repeat from 3

    I also experimented with different probabilities of choosing candidate points based on counting how many visited neighbors the selected pixel has, but it only slowed down the algorithm without making it prettier. The current algorithm doesn't use probabilities and chooses a random point from the list. This causes points with lots of neighbors to quickly fill up, making it just a growing solid ball with a fuzzy edge. This also prevents unavailability of neighboring colors if the crevices were to be filled up later in the process.

    The image is toroidal.

    Java

    Download: com.digitalmodular library

    package demos;
    
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.io.IOException;
    import java.util.Arrays;
    
    import com.digitalmodular.utilities.RandomFunctions;
    import com.digitalmodular.utilities.gui.ImageFunctions;
    import com.digitalmodular.utilities.swing.window.PixelImage;
    import com.digitalmodular.utilities.swing.window.PixelWindow;
    
    /**
     * @author jeronimus
     */
    // Date 2014-02-28
    public class AllColorDiffusion extends PixelWindow implements Runnable {
        private static final int    CHANNEL_BITS    = 7;
    
        public static void main(String[] args) {
            int bits = CHANNEL_BITS * 3;
            int heightBits = bits / 2;
            int widthBits = bits - heightBits;
    
            new AllColorDiffusion(CHANNEL_BITS, 1 << widthBits, 1 << heightBits);
        }
    
        private final int           width;
        private final int           height;
        private final int           channelBits;
        private final int           channelSize;
    
        private PixelImage          img;
        private javax.swing.Timer   timer;
    
        private boolean[]           colorCube;
        private long[]              foundColors;
        private boolean[]           queued;
        private int[]               queue;
        private int                 queuePointer    = 0;
        private int                 remaining;
    
        public AllColorDiffusion(int channelBits, int width, int height) {
            super(1024, 1024 * height / width);
    
            RandomFunctions.RND.setSeed(0);
    
            this.width = width;
            this.height = height;
            this.channelBits = channelBits;
            channelSize = 1 << channelBits;
        }
    
        @Override
        public void initialized() {
            img = new PixelImage(width, height);
    
            colorCube = new boolean[channelSize * channelSize * channelSize];
            foundColors = new long[channelSize * channelSize * channelSize];
            queued = new boolean[width * height];
            queue = new int[width * height];
            for (int i = 0; i < queue.length; i++)
                queue[i] = i;
    
            new Thread(this).start();
        }
    
        @Override
        public void resized() {}
    
        @Override
        public void run() {
            timer = new javax.swing.Timer(500, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    draw();
                }
            });
    
            while (true) {
                img.clear(0);
                init();
                render();
            }
    
            // System.exit(0);
        }
    
        private void init() {
            RandomFunctions.RND.setSeed(0);
    
            Arrays.fill(colorCube, false);
            Arrays.fill(queued, false);
            remaining = width * height;
    
            // Initial seeds (need to be the darkest colors, because of the darkest
            // neighbor color search algorithm.)
            setPixel(width / 2 + height / 2 * width, 0);
            remaining--;
        }
    
        private void render() {
            timer.start();
    
            for (; remaining > 0; remaining--) {
                int point = findPoint();
                int color = findColor(point);
                setPixel(point, color);
            }
    
            timer.stop();
            draw();
    
            try {
                ImageFunctions.savePNG(System.currentTimeMillis() + ".png", img.image);
            }
            catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    
        void draw() {
            g.drawImage(img.image, 0, 0, getWidth(), getHeight(), 0, 0, width, height, null);
            repaintNow();
        }
    
        private int findPoint() {
            while (true) {
                // Time to reshuffle?
                if (queuePointer == 0) {
                    for (int i = queue.length - 1; i > 0; i--) {
                        int j = RandomFunctions.RND.nextInt(i);
                        int temp = queue[i];
                        queue[i] = queue[j];
                        queue[j] = temp;
                        queuePointer = queue.length;
                    }
                }
    
                if (queued[queue[--queuePointer]])
                    return queue[queuePointer];
            }
        }
    
        private int findColor(int point) {
            int x = point & width - 1;
            int y = point / width;
    
            // Calculate the reference color as the average of all 8-connected
            // colors.
            int r = 0;
            int g = 0;
            int b = 0;
            int n = 0;
            for (int j = -1; j <= 1; j++) {
                for (int i = -1; i <= 1; i++) {
                    point = (x + i & width - 1) + width * (y + j & height - 1);
                    if (img.pixels[point] != 0) {
                        int pixel = img.pixels[point];
    
                        r += pixel >> 24 - channelBits & channelSize - 1;
                        g += pixel >> 16 - channelBits & channelSize - 1;
                        b += pixel >> 8 - channelBits & channelSize - 1;
                        n++;
                    }
                }
            }
            r /= n;
            g /= n;
            b /= n;
    
            // Find a color that is preferably darker but not too far from the
            // original. This algorithm might fail to take some darker colors at the
            // start, and when the image is almost done the size will become really
            // huge because only bright reference pixels are being searched for.
            // This happens with a probability of 50% with 6 channelBits, and more
            // with higher channelBits values.
            //
            // Try incrementally larger distances from reference color.
            for (int size = 2; size <= channelSize; size *= 2) {
                n = 0;
    
                // Find all colors in a neighborhood from the reference color (-1 if
                // already taken).
                for (int ri = r - size; ri <= r + size; ri++) {
                    if (ri < 0 || ri >= channelSize)
                        continue;
                    int plane = ri * channelSize * channelSize;
                    int dr = Math.abs(ri - r);
                    for (int gi = g - size; gi <= g + size; gi++) {
                        if (gi < 0 || gi >= channelSize)
                            continue;
                        int slice = plane + gi * channelSize;
                        int drg = Math.max(dr, Math.abs(gi - g));
                        int mrg = Math.min(ri, gi);
                        for (int bi = b - size; bi <= b + size; bi++) {
                            if (bi < 0 || bi >= channelSize)
                                continue;
                            if (Math.max(drg, Math.abs(bi - b)) > size)
                                continue;
                            if (!colorCube[slice + bi])
                                foundColors[n++] = Math.min(mrg, bi) << channelBits * 3 | slice + bi;
                        }
                    }
                }
    
                if (n > 0) {
                    // Sort by distance from origin.
                    Arrays.sort(foundColors, 0, n);
    
                    // Find a random color amongst all colors equally distant from
                    // the origin.
                    int lowest = (int)(foundColors[0] >> channelBits * 3);
                    for (int i = 1; i < n; i++) {
                        if (foundColors[i] >> channelBits * 3 > lowest) {
                            n = i;
                            break;
                        }
                    }
    
                    int nextInt = RandomFunctions.RND.nextInt(n);
                    return (int)(foundColors[nextInt] & (1 << channelBits * 3) - 1);
                }
            }
    
            return -1;
        }
    
        private void setPixel(int point, int color) {
            int b = color & channelSize - 1;
            int g = color >> channelBits & channelSize - 1;
            int r = color >> channelBits * 2 & channelSize - 1;
            img.pixels[point] = 0xFF000000 | ((r << 8 | g) << 8 | b) << 8 - channelBits;
    
            colorCube[color] = true;
    
            int x = point & width - 1;
            int y = point / width;
            queued[point] = false;
            for (int j = -1; j <= 1; j++) {
                for (int i = -1; i <= 1; i++) {
                    point = (x + i & width - 1) + width * (y + j & height - 1);
                    if (img.pixels[point] == 0) {
                        queued[point] = true;
                    }
                }
            }
        }
    }
    
    • 512×512
    • original 1 seed
    • 1 second

    enter image description here

    • 2048×1024
    • slightly tiled to 1920×1080 desktop
    • 30 seconds
    • negative in photoshop

    enter image description here

    • 2048×1024
    • 8 seeds
    • 27 seconds

    enter image description here

    • 512×512
    • 40 random seeds
    • 6 seconds

    enter image description here

    • 4096×4096
    • 1 seed
    • Streaks get significantly sharper (as in they look like they could chop a fish into sashimi)
    • Looked like it finished in 20 minutes, but ... failed to finish for some reason, so now I'm running 7 instances in parallel overnight.

    [See below]

    [Edit]
    ** I discovered that my method of choosing pixels was not totally random at all. I thought having a random permutation of the search space would be random and faster than real random (because a point will not be chosen twice by chance. However somehow, replacing it with real random, I consistently get more noise speckles in my image.

    [version 2 code removed because I was over the 30,000 character limit]

    enter image description here

    • Increased the initial search cube to 5x5x5

    enter image description here

    • Even bigger, 9x9x9

    enter image description here

    • Accident 1. Disabled the permutation so the search space is always linear.

    enter image description here

    • Accident 2. Tried a new search technique using a fifo queue. Still have to analyze this but I thought it was worth sharing.

    enter image description here

    • Always choosing within X unused pixels from the center
    • X ranges from 0 to 8192 in steps of 256

    Image can't be uploaded: "Oops! Something Bad Happened! It’s not you, it’s us. This is our fault." Image is just too big for imgur. Trying elsewhere...

    enter image description here

    Experimenting with a scheduler package I found in the digitalmodular library to determine the order in which the pixels are handled (instead of diffusion).

    package demos;
    
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.io.IOException;
    import java.util.Arrays;
    
    import com.digitalmodular.utilities.RandomFunctions;
    import com.digitalmodular.utilities.gui.ImageFunctions;
    import com.digitalmodular.utilities.gui.schedulers.ScheduledPoint;
    import com.digitalmodular.utilities.gui.schedulers.Scheduler;
    import com.digitalmodular.utilities.gui.schedulers.XorScheduler;
    import com.digitalmodular.utilities.swing.window.PixelImage;
    import com.digitalmodular.utilities.swing.window.PixelWindow;
    
    /**
     * @author jeronimus
     */
    // Date 2014-02-28
    public class AllColorDiffusion3 extends PixelWindow implements Runnable {
        private static final int    CHANNEL_BITS    = 7;
    
        public static void main(String[] args) {
    
            int bits = CHANNEL_BITS * 3;
            int heightBits = bits / 2;
            int widthBits = bits - heightBits;
    
            new AllColorDiffusion3(CHANNEL_BITS, 1 << widthBits, 1 << heightBits);
        }
    
        private final int           width;
        private final int           height;
        private final int           channelBits;
        private final int           channelSize;
    
        private PixelImage          img;
        private javax.swing.Timer   timer;
        private Scheduler           scheduler   = new XorScheduler();
    
        private boolean[]           colorCube;
        private long[]              foundColors;
    
        public AllColorDiffusion3(int channelBits, int width, int height) {
            super(1024, 1024 * height / width);
    
            this.width = width;
            this.height = height;
            this.channelBits = channelBits;
            channelSize = 1 << channelBits;
        }
    
        @Override
        public void initialized() {
            img = new PixelImage(width, height);
    
            colorCube = new boolean[channelSize * channelSize * channelSize];
            foundColors = new long[channelSize * channelSize * channelSize];
    
            new Thread(this).start();
        }
    
        @Override
        public void resized() {}
    
        @Override
        public void run() {
            timer = new javax.swing.Timer(500, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    draw();
                }
            });
    
            // for (double d = 0.2; d < 200; d *= 1.2)
            {
                img.clear(0);
                init(0);
                render();
            }
    
            // System.exit(0);
        }
    
        private void init(double param) {
            // RandomFunctions.RND.setSeed(0);
    
            Arrays.fill(colorCube, false);
    
            // scheduler = new SpiralScheduler(param);
            scheduler.init(width, height);
        }
    
        private void render() {
            timer.start();
    
            while (scheduler.getProgress() != 1) {
                int point = findPoint();
                int color = findColor(point);
                setPixel(point, color);
            }
    
            timer.stop();
            draw();
    
            try {
                ImageFunctions.savePNG(System.currentTimeMillis() + ".png", img.image);
            }
            catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    
        void draw() {
            g.drawImage(img.image, 0, 0, getWidth(), getHeight(), 0, 0, width, height, null);
            repaintNow();
            setTitle(Double.toString(scheduler.getProgress()));
        }
    
        private int findPoint() {
            ScheduledPoint p = scheduler.poll();
    
            // try {
            // Thread.sleep(1);
            // }
            // catch (InterruptedException e) {
            // }
    
            return p.x + width * p.y;
        }
    
        private int findColor(int point) {
            // int z = 0;
            // for (int i = 0; i < colorCube.length; i++)
            // if (!colorCube[i])
            // System.out.println(i);
    
            int x = point & width - 1;
            int y = point / width;
    
            // Calculate the reference color as the average of all 8-connected
            // colors.
            int r = 0;
            int g = 0;
            int b = 0;
            int n = 0;
            for (int j = -3; j <= 3; j++) {
                for (int i = -3; i <= 3; i++) {
                    point = (x + i & width - 1) + width * (y + j & height - 1);
                    int f = (int)Math.round(10000 * Math.exp((i * i + j * j) * -0.4));
                    if (img.pixels[point] != 0) {
                        int pixel = img.pixels[point];
    
                        r += (pixel >> 24 - channelBits & channelSize - 1) * f;
                        g += (pixel >> 16 - channelBits & channelSize - 1) * f;
                        b += (pixel >> 8 - channelBits & channelSize - 1) * f;
                        n += f;
                    }
                    // System.out.print(f + "\t");
                }
                // System.out.println();
            }
            if (n > 0) {
                r /= n;
                g /= n;
                b /= n;
            }
    
            // Find a color that is preferably darker but not too far from the
            // original. This algorithm might fail to take some darker colors at the
            // start, and when the image is almost done the size will become really
            // huge because only bright reference pixels are being searched for.
            // This happens with a probability of 50% with 6 channelBits, and more
            // with higher channelBits values.
            //
            // Try incrementally larger distances from reference color.
            for (int size = 2; size <= channelSize; size *= 2) {
                n = 0;
    
                // Find all colors in a neighborhood from the reference color (-1 if
                // already taken).
                for (int ri = r - size; ri <= r + size; ri++) {
                    if (ri < 0 || ri >= channelSize)
                        continue;
                    int plane = ri * channelSize * channelSize;
                    int dr = Math.abs(ri - r);
                    for (int gi = g - size; gi <= g + size; gi++) {
                        if (gi < 0 || gi >= channelSize)
                            continue;
                        int slice = plane + gi * channelSize;
                        int drg = Math.max(dr, Math.abs(gi - g));
                        // int mrg = Math.min(ri, gi);
                        long srg = ri * 299L + gi * 436L;
                        for (int bi = b - size; bi <= b + size; bi++) {
                            if (bi < 0 || bi >= channelSize)
                                continue;
                            if (Math.max(drg, Math.abs(bi - b)) > size)
                                continue;
                            if (!colorCube[slice + bi])
                                // foundColors[n++] = Math.min(mrg, bi) <<
                                // channelBits * 3 | slice + bi;
                                foundColors[n++] = srg + bi * 114L << channelBits * 3 | slice + bi;
                        }
                    }
                }
    
                if (n > 0) {
                    // Sort by distance from origin.
                    Arrays.sort(foundColors, 0, n);
    
                    // Find a random color amongst all colors equally distant from
                    // the origin.
                    int lowest = (int)(foundColors[0] >> channelBits * 3);
                    for (int i = 1; i < n; i++) {
                        if (foundColors[i] >> channelBits * 3 > lowest) {
                            n = i;
                            break;
                        }
                    }
    
                    int nextInt = RandomFunctions.RND.nextInt(n);
                    return (int)(foundColors[nextInt] & (1 << channelBits * 3) - 1);
                }
            }
    
            return -1;
        }
    
        private void setPixel(int point, int color) {
            int b = color & channelSize - 1;
            int g = color >> channelBits & channelSize - 1;
            int r = color >> channelBits * 2 & channelSize - 1;
            img.pixels[point] = 0xFF000000 | ((r << 8 | g) << 8 | b) << 8 - channelBits;
    
            colorCube[color] = true;
        }
    }
    
    • Angular(8)

    enter image description here

    • Angular(64)

    enter image description here

    • CRT

    enter image description here

    • Dither

    enter image description here

    • Flower(5, X), where X ranges from 0.5 to 20 in steps of X=X×1.2

    enter image description here

    • Mod

    enter image description here

    • Pythagoras

    enter image description here

    • Radial

    enter image description here

    • Random

    enter image description here

    • Scanline

    enter image description here

    • Spiral(X), where X ranges from 0.1 to 200 in steps of X=X×1.2
    • You can see it ranges in between Radial to Angular(5)

    enter image description here

    • Split

    enter image description here

    • SquareSpiral

    enter image description here

    • XOR

    enter image description here

    New eye-food

    • Effect of color selection by max(r, g, b)

    enter image description here

    • Effect of color selection by min(r, g, b)
    • Notice that this one has exactly the same features/details as the one above, only with different colors! (same random seed)

    enter image description here

    • Effect of color selection by max(r, min(g, b))

    enter image description here

    • Effect of color selection by gray value 299*r + 436*g + 114*b

    enter image description here

    • Effect of color selection by 1*r + 10*g + 100*b

    enter image description here

    • Effect of color selection by 100*r + 10*g + 1*b

    enter image description here

    • Happy accidents when 299*r + 436*g + 114*b overflowed in a 32-bit integer

    enter image description here enter image description here enter image description here

    • Variant 3, with gray value and Radial scheduler

    enter image description here

    • I forgot how I created this

    enter image description here

    • The CRT Scheduler also had a happy integer overflow bug (updated the ZIP), this caused it to start half-way, with 512×512 images, instead of at the center. This is what it's supposed to look like:

    enter image description here enter image description here

    • InverseSpiralScheduler(64) (new)

    enter image description here

    • Another XOR

    enter image description here

    • First successful 4096 render after the bugfix. I think this was version 3 on SpiralScheduler(1) or something

    enter image description here (50MB!!)

    • Version 1 4096, but I accidentally left the color criteria on max()

    enter image description here (50MB!!)

    • 4096, now with min()
    • Notice that this one has exactly the same features/details as the one above, only with different colors! (same random seed)
    • Time: forgot to record it but the file timestamp is 3 minutes after the image before

    enter image description here (50MB!!)

    Cool. Your final image is similar to a second idea I've been tossing around, although I have a feeling mine won't look as good as that. BTW, there is a similar cool one at http://allrgb.com/diffusive.

    It was meant as just a teaser, but I edited it in fear of being flagged, which apparently happened :)

    Even the accidents look nice :). The color cube seems like a very good idea, and your render speeds are amazing, compared to mine. Some designs on allrgb do have a good description, for example allrgb.com/dla. I wish I had more time to do more experiments, there are so many possibilities...

    I almost forgot, I just uploaded some of my big renders. I think one of them, the rainbow smoke/spilled ink thingy, is better than anything on allrgb :). I agree, the others are not so stunning, that's why I made a video to make something more out of them :).

    Added source code and the link to the Digisoft library, so you can actually compile my code

    @Zom-B If the contest is over, you should select the most popular answer as the accepted winner.

    Bug update, dear followers.

    This is definitely my favorite answer. I really like the variety in all of the images you posted.

    Can we find the program that generated any of the other images, on pastebin/github/etc.? Would be cool to read through that! (the >30,000 char one)

    The 2nd program is just a simplified version of the first, with random instead of the permutation. The 30,000 refers to the StackExchange post limit.

  • C++ w/ Qt

    I see you version:

    enter image description here

    using normal distribution for the colors:

    enter image description here enter image description here

    or first sorted by red / hue (with a smaller deviation):

    enter image description here enter image description here

    or some other distributions:

    enter image description here enter image description here

    Cauchy distribution (hsl / red):

    enter image description here enter image description here

    sorted cols by lightness (hsl):

    enter image description here

    updated source code - produces 6th image:

    int main() {
        const int c = 256*128;
        std::vector<QRgb> data(c);
        QImage image(256, 128, QImage::Format_RGB32);
    
        std::default_random_engine gen;
        std::normal_distribution<float> dx(0, 2);
        std::normal_distribution<float> dy(0, 1);
    
        for(int i = 0; i < c; ++i) {
            data[i] = qRgb(i << 3 & 0xF8, i >> 2 & 0xF8, i >> 7 & 0xF8);
        }
        std::sort(data.begin(), data.end(), [] (QRgb a, QRgb b) -> bool {
            return QColor(a).hsvHue() < QColor(b).hsvHue();
        });
    
        int i = 0;
        while(true) {
            if(i % 10 == 0) { //no need on every iteration
                dx = std::normal_distribution<float>(0, 8 + 3 * i/1000.f);
                dy = std::normal_distribution<float>(0, 4 + 3 * i/1000.f);
            }
            int x = (int) dx(gen);
            int y = (int) dy(gen);
            if(x < 256 && x >= 0 && y >= 0 && y < 128) {
                if(!image.pixel(x, y)) {
                    image.setPixel(x, y, data[i]);
                    if(i % (c/100) == 1) {
                        std::cout << (int) (100.f*i/c) << "%\n";
                    }
                    if(++i == c) break;
                }
            }
        }
        image.save("tmp.png");
        return 0;
    }
    

    Nicely done. However, mightn't `image.pixel(x, y) == 0` fail and overwrite the first placed pixel?

    @Zom-B: it can, but then the last one will be black, so it's within the rules..

    No rule problem though. I just thought you might have missed it. Might as well count from 1 then. I love your other ones!

    @Zom-B: thanks, I might add a few more, I kinda like it :P

    The one with two circles and the one below it together kinda look like a monkey face.

    "I see you version" has 262102 colors for some reason.

  • In Java:

    import java.awt.Color;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    import java.util.Collections;
    import java.util.LinkedList;
    
    import javax.imageio.ImageIO;
    
    public class ImgColor {
    
        private static class Point {
            public int x, y;
            public color c;
    
            public Point(int x, int y, color c) {
                this.x = x;
                this.y = y;
                this.c = c;
            }
        }
    
        private static class color {
            char r, g, b;
    
            public color(int i, int j, int k) {
                r = (char) i;
                g = (char) j;
                b = (char) k;
            }
        }
    
        public static LinkedList<Point> listFromImg(String path) {
            LinkedList<Point> ret = new LinkedList<>();
            BufferedImage bi = null;
            try {
                bi = ImageIO.read(new File(path));
            } catch (IOException e) {
                e.printStackTrace();
            }
            for (int x = 0; x < 4096; x++) {
                for (int y = 0; y < 4096; y++) {
                    Color c = new Color(bi.getRGB(x, y));
                    ret.add(new Point(x, y, new color(c.getRed(), c.getGreen(), c.getBlue())));
                }
            }
            Collections.shuffle(ret);
            return ret;
        }
    
        public static LinkedList<color> allColors() {
            LinkedList<color> colors = new LinkedList<>();
            for (int r = 0; r < 256; r++) {
                for (int g = 0; g < 256; g++) {
                    for (int b = 0; b < 256; b++) {
                        colors.add(new color(r, g, b));
                    }
                }
            }
            Collections.shuffle(colors);
            return colors;
        }
    
        public static Double cDelta(color a, color b) {
            return Math.pow(a.r - b.r, 2) + Math.pow(a.g - b.g, 2) + Math.pow(a.b - b.b, 2);
        }
    
        public static void main(String[] args) {
            BufferedImage img = new BufferedImage(4096, 4096, BufferedImage.TYPE_INT_RGB);
            LinkedList<Point> orig = listFromImg(args[0]);
            LinkedList<color> toDo = allColors();
    
            Point p = null;
            while (orig.size() > 0 && (p = orig.pop()) != null) {
                color chosen = toDo.pop();
                for (int i = 0; i < Math.min(100, toDo.size()); i++) {
                    color c = toDo.pop();
                    if (cDelta(c, p.c) < cDelta(chosen, p.c)) {
                        toDo.add(chosen);
                        chosen = c;
                    } else {
                        toDo.add(c);
                    }
                }
                img.setRGB(p.x, p.y, new Color(chosen.r, chosen.g, chosen.b).getRGB());
            }
            try {
                ImageIO.write(img, "PNG", new File(args[1]));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }
    

    and an input image:

    lemur

    I generate something like this:

    acidLemur

    uncompressed version here: https://www.mediafire.com/?7g3fetvaqhoqgh8

    It takes my computer roughly 30 minutes to do a 4096^2 image, which is a huge improvement over the 32 days my first implementation would have taken.

    ouch; 32 days didnt sounded funny..... the average algorithm in fejesjocos answer on 4k before optimize would have taken probably multiple months

    I love his punk eyebrows!

  • Java with BubbleSort

    (usually Bubblesort isnt liked that much but for this challenge it finally had a use :) generated a line with all elements in 4096 steps apart then shuffled it; the sorting went thru and each like got 1 added to their value while being sorted so as result you got the values sorted and all colors

    Updated the Sourcecode to get those big stripes removed
    (needed some bitwise magic :P)

    class Pix
    {
        public static void main(String[] devnull) throws Exception
        {
            int chbits=8;
            int colorsperchannel=1<<chbits;
            int xsize=4096,ysize=4096;
            System.out.println(colorsperchannel);
            int[] x = new int[xsize*ysize];//colorstream
    
            BufferedImage i = new BufferedImage(xsize,ysize, BufferedImage.TYPE_INT_RGB);
            List<Integer> temp = new ArrayList<>();
            for (int j = 0; j < 4096; j++)
            {
                temp.add(4096*j);
            }
            int[] temp2=new int[4096];
    
            Collections.shuffle(temp,new Random(9263));//intended :P looked for good one
            for (int j = 0; j < temp.size(); j++)
            {
                temp2[j]=(int)(temp.get(j));
            }
            x = spezbubblesort(temp2, 4096);
            int b=-1;
            int b2=-1;
            for (int j = 0; j < x.length; j++)
            {
                if(j%(4096*16)==0)b++;
                if(j%(4096)==0)b2++;
                int h=j/xsize;
                int w=j%xsize;
                i.setRGB(w, h, x[j]&0xFFF000|(b|(b2%16)<<8));
                x[j]=x[j]&0xFFF000|(b|(b2%16)<<8);
            }  
    
            //validator sorting and checking that all values only have 1 difference
            Arrays.sort(x);
            int diff=0;
            for (int j = 1; j < x.length; j++)
            {
                int ndiff=x[j]-x[j-1];
                if(ndiff!=diff)
                {
                    System.out.println(ndiff);
                }
                diff=ndiff;
    
            }
            OutputStream out = new BufferedOutputStream(new FileOutputStream("RGB24.bmp"));
            ImageIO.write(i, "bmp", out);
    
        }
        public static int[] spezbubblesort(int[] vals,int lines)
        {
            int[] retval=new int[vals.length*lines];
            for (int i = 0; i < lines; i++)
            {
                retval[(i<<12)]=vals[0];
                for (int j = 1; j < vals.length; j++)
                {
                    retval[(i<<12)+j]=vals[j];
                    if(vals[j]<vals[j-1])
                    {
    
                        int temp=vals[j-1];
                        vals[j-1]=vals[j];
                        vals[j]=temp;
                    }
                    vals[j-1]=vals[j-1]+1;
                }
                vals[lines-1]=vals[lines-1]+1;
            }
            return retval;
        }
    }
    

    Result:

    Old version

    class Pix
    {
        public static void main(String[] devnull) throws Exception
        {
            int[] x = new int[4096*4096];//colorstream
            int idx=0;
            BufferedImage i = new BufferedImage(4096, 4096, BufferedImage.TYPE_INT_RGB);
            //GENCODE
            List<Integer> temp = new ArrayList<>();
            for (int j = 0; j < 4096; j++)
            {
                temp.add(4096*j);
            }
            int[] temp2=new int[4096];
    
            Collections.shuffle(temp,new Random(9263));//intended :P looked for good one
            for (int j = 0; j < temp.size(); j++)
            {
                temp2[j]=(int)(temp.get(j));
            }
            x = spezbubblesort(temp2, 4096);
            for (int j = 0; j < x.length; j++)
            {
                int h=j/4096;
                int w=j%4096;
                i.setRGB(w, h, x[j]);
            }
            //validator sorting and checking that all values only have 1 difference
            Arrays.sort(x);
            int diff=0;
            for (int j = 1; j < x.length; j++)
            {
                int ndiff=x[j]-x[j-1];
                if(ndiff!=diff)
                {
                    System.out.println(ndiff);
                }
                diff=ndiff;
    
            }
            OutputStream out = new BufferedOutputStream(new FileOutputStream("RGB24.bmp"));
            ImageIO.write(i, "bmp", out);
        }
        public static int[] spezbubblesort(int[] vals,int lines)
        {
            int[] retval=new int[vals.length*lines];
            for (int i = 0; i < lines; i++)
            {
                retval[(i<<12)]=vals[0];
                for (int j = 1; j < vals.length; j++)
                {
                    retval[(i<<12)+j]=vals[j];
                    if(vals[j]<vals[j-1])
                    {
    
                        int temp=vals[j-1];
                        vals[j-1]=vals[j];
                        vals[j]=temp;
                    }
                    vals[j-1]=vals[j-1]+1;
                }
                vals[lines-1]=vals[lines-1]+1;
            }
            return retval;
        }
    }
    

    output preview

    There is already a QuickSort version on the allRGB page.

    @Zom-B Quicksort is a different algorithm than Bubblesort

  • C

    Creates a vortex, for reasons I don't understand, with even and odd frames containing completely different vortices.

    This a preview of the first 50 odd frames:

    vortex preview

    Sample image converted from PPM to demo complete color coverage:

    sample image

    Later on, when it's all blended into grey, you can still see it spinning: longer sequence.

    Code as follows. To run, include the frame number, e.g.:

    ./vortex 35 > 35.ppm
    

    I used this to get an animated GIF:

    convert -delay 10 `ls *.ppm | sort -n | xargs` -loop 0 vortex.gif
    #include <stdlib.h>
    #include <stdio.h>
    
    #define W 256
    #define H 128
    
    typedef struct {unsigned char r, g, b;} RGB;
    
    int S1(const void *a, const void *b)
    {
        const RGB *p = a, *q = b;
        int result = 0;
    
        if (!result)
            result = (p->b + p->g * 6 + p->r * 3) - (q->b + q->g * 6 + q->r * 3);
    
        return result;
    }
    
    int S2(const void *a, const void *b)
    {
        const RGB *p = a, *q = b;
        int result = 0;
    
        if (!result)
            result = p->b * 6 - p->g;
        if (!result)
            result = p->r - q->r;
        if (!result)
            result = p->g - q->b * 6;
    
        return result;
    }
    
    int main(int argc, char *argv[])
    {
        int i, j, n;
        RGB *rgb = malloc(sizeof(RGB) * W * H);
        RGB c[H];
    
        for (i = 0; i < W * H; i++)
        {
            rgb[i].b = (i & 0x1f) << 3;
            rgb[i].g = ((i >> 5) & 0x1f) << 3;
            rgb[i].r = ((i >> 10) & 0x1f) << 3;
        }
    
        qsort(rgb, H * W, sizeof(RGB), S1);
    
        for (n = 0; n < atoi(argv[1]); n++)
        {
            for (i = 0; i < W; i++)
            {
                for (j = 0; j < H; j++)
                    c[j] = rgb[j * W + i];
                qsort(c, H, sizeof(RGB), S2);
                for (j = 0; j < H; j++)
                    rgb[j * W + i] = c[j];
            }
    
            for (i = 0; i < W * H; i += W)
                qsort(rgb + i, W, sizeof(RGB), S2);
        }
    
        printf("P6 %d %d 255\n", W, H);
        fwrite(rgb, sizeof(RGB), W * H, stdout);
    
        free(rgb);
    
        return 0;
    }
    

    You know it's C when thing happen for "reasons I don't understand".

    Yeah, usually I know what to expect, but here I was just playing around to see what patterns I could get, and this non-terminating order-within-chaos sequence came up.

    I count only 256 unique colors in each of the animation frames (ofcourse because it's GIF)

    Yes, the raw PPM files though have all the colors. Was going to attach one, but PPM images aren't accepted.

    convert em to PNG (lossless so all colors will stay at their intended spot) or upload it to mediaafire or any other filehoster and post link

    Bug? `result = p->b * 6 - p->g;`

    It vortexes because your comparison function doesn't follow the triangle inequality. For example, r>b, b>g, g>r. I can't even port it to Java because it's mergesort relies on this very property, so I get the exception "Comparison method violates its general contract!"

    I'll try `p->b * 6 - q->g;` but if it wrecks the vortex, won't fix it!

    `convert -deconstruct` and good 95% of your gif size is gone

    +1 for reasons I don't understand.

    is there a similar tool like `convert` for windows? I'm using MS gif animator but its a crappy windows 95 gui

    @Zom-B, the GIMP has an "optimise for GIF" feature.

    @Zom-B - the line is essential for the vortex, correcting it or removing it - no vortex!

    @mniip - no difference in size after `-deconstruct`

    You have to give the `-deconstruct` parameter when creating the gif: (besides, your command has some style mistakes) `convert -deconstruct -delay 10 $(echo *.ppm | sort -n) -loop 0 vortex.gif`

    @mniip - no change in size - did exactly as you wrote: `$ convert -deconstruct -delay 10 $(echo *.ppm | sort -n) -loop 0 vortex.gif` `$ ls -l *.gif` `-rw-rw-r-- 1 yimin yimin 20437942 Feb 27 14:55 vortex.gif`

    @Zom-B Imagemagic (which convert is a part) is available for Windows, too

  • Java

    Variations of a color picker in 512x512. Elegant code it is not, but I do like the pretty pictures:

    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    import java.util.Random;
    
    import javax.imageio.ImageIO;
    
    public class EighteenBitColors {
    
        static boolean shuffle_block = false;
        static int shuffle_radius = 0;
    
        public static void main(String[] args) {
            BufferedImage img = new BufferedImage(512, 512, BufferedImage.TYPE_INT_RGB);
            for(int r=0;r<64;r++)
                for(int g=0;g<64;g++)
                    for(int b=0;b<64;b++)
                        img.setRGB((r * 8) + (b / 8), (g * 8) + (b % 8), ((r * 4) << 8 | (g * 4)) << 8 | (b * 4));
    
            if(shuffle_block)
                blockShuffle(img);
            else
                shuffle(img, shuffle_radius);
    
            try {           
                ImageIO.write(img, "png", new File(getFileName()));
            } catch(IOException e){
                System.out.println("suck it");
            }
        }
    
        public static void shuffle(BufferedImage img, int radius){
            if(radius < 1)
                return;
            int width = img.getWidth();
            int height = img.getHeight();
            Random rand = new Random();
            for(int x=0;x<512;x++){
                for(int y=0;y<512;y++){
                    int xx = -1;
                    int yy = -1;
                    while(xx < 0 || xx >= width){
                        xx = x + rand.nextInt(radius*2+1) - radius;
                    }
                    while(yy < 0 || yy >= height){
                        yy = y + rand.nextInt(radius*2+1) - radius;
                    }
                    int tmp = img.getRGB(xx, yy);
                    img.setRGB(xx, yy, img.getRGB(x, y));
                    img.setRGB(x,y,tmp);
                }
            }
        }
    
        public static void blockShuffle(BufferedImage img){
            int tmp;
            Random rand = new Random();
            for(int bx=0;bx<8;bx++){
                for(int by=0;by<8;by++){
                    for(int x=0;x<64;x++){
                        for(int y=0;y<64;y++){
                            int xx = bx*64+x;
                            int yy = by*64+y;
                            int xxx = bx*64+rand.nextInt(64);
                            int yyy = by*64+rand.nextInt(64);
                            tmp = img.getRGB(xxx, yyy);
                            img.setRGB(xxx, yyy, img.getRGB(xx, yy));
                            img.setRGB(xx,yy,tmp);
                        }
                    }
                }
            }
        }
    
        public static String getFileName(){
            String fileName = "allrgb_";
            if(shuffle_block){
                fileName += "block";
            } else if(shuffle_radius > 0){
                fileName += "radius_" + shuffle_radius;
            } else {
                fileName += "no_shuffle";
            }
            return fileName + ".png";
        }
    }
    

    As written, it outputs:

    no shuffle

    If you run it with shuffle_block = true, it shuffles the colors in each 64x64 block:

    block shuffle

    Else, if you run it with shuffle_radius > 0, it shuffles each pixel with a random pixel within shuffle_radius in x/y. After playing with various sizes, I like a 32 pixel radius, as it blurs the lines without moving stuff around too much:

    enter image description here

    ooh these pictures are the prettiest

    These are really great

  • Processing

    I'm just getting started with C (having programmed in other languages) but found the graphics in Visual C tough to follow, so I downloaded this Processing program used by @ace.

    Here's my code and my algorithm.

    void setup(){
      size(256,128);
      background(0);
      frameRate(1000000000);
      noLoop();
     }
    
    int x,y,r,g,b,c;
    void draw() {
      for(y=0;y<128;y++)for(x=0;x<128;x++){
        r=(x&3)+(y&3)*4;
        g=x>>2;
        b=y>>2;
        c=0;
        //c=x*x+y*y<10000? 1:0; 
        stroke((r^16*c)<<3,g<<3,b<<3);
        point(x,y);
        stroke((r^16*(1-c))<<3,g<<3,b<<3);
        point(255-x,y);  
      } 
    }
    

    Algorithm

    Start with 4x4 squares of all possible combinations of 32 values of green and blue, in x,y. format, making a 128x128 square Each 4x4 square has 16 pixels, so make a mirror image beside it to give 32 pixels of each possible combination of green and blue, per image below.

    (bizarrely the full green looks brighter than the full cyan. This must be an optical illusion. clarified in comments)

    In the lefthand square, add in the red values 0-15. For the righthand square, XOR these values with 16, to make the values 16-31.

    enter image description here

    Output 256x128

    This gives the output in the top image below.

    However, every pixel differs from its mirror image only in the most significant bit of the red value. So, I can apply a condition with the variable c, to reverse the XOR, which has the same effect as exchanging these two pixels.

    An example of this is given in the bottom image below (if we uncomment the line of code which is currently commented out.)

    enter image description here

    512 x 512 - A tribute to Andy Warhol's Marylin

    Inspired by Quincunx's answer to this question with an "evil grin" in freehand red circles, here is my version of the famous picture. The original actually had 25 coloured Marylins and 25 black & white Marylins and was Warhol's tribute to Marylin after her untimely death. See http://en.wikipedia.org/wiki/Marilyn_Diptych

    I changed to different functions after discovering that Processing renders the ones I used in 256x128 as semitransparent. The new ones are opaque.

    And although the image isn't completely algorithmic, I rather like it.

    int x,y,r,g,b,c;
    PImage img;
    color p;
    void setup(){
      size(512,512);
      background(0);
      img = loadImage("marylin256.png");
      frameRate(1000000000);
      noLoop();
     }
    
    void draw() {
    
       image(img,0,0);
    
       for(y=0;y<256;y++)for(x=0;x<256;x++){
          // Note the multiplication by 0 in the next line. 
          // Replace the 0 with an 8 and the reds are blended checkerboard style
          // This reduces the grain size, but on balance I decided I like the grain.
          r=((x&3)+(y&3)*4)^0*((x&1)^(y&1));
          g=x>>2;
          b=y>>2; 
          c=brightness(get(x,y))>100? 32:0;
          p=color((r^c)<<2,g<<2,b<<2);
          set(x,y,p);
          p=color((r^16^c)<<2,g<<2,b<<2);
          set(256+x,y,p);  
          p=color((r^32^c)<<2,g<<2,b<<2);
          set(x,256+y,p);
          p=color((r^48^c)<<2,g<<2,b<<2);
          set(256+x,256+y,p);  
     } 
     save("warholmarylin.png");
    

    }

    enter image description here

    512x512 Twilight over a lake with mountains in the distance

    Here, a fully algorithmic picture. I've played around with changing which colour I modulate with the condition, but I just come back to the conclusion that red works best. Similar to the Marylin picture, I draw the mountains first, then pick the brightness from that picture to overwrite the positive RGB image, while copying to the negative half. A slight difference is that the bottom of many of the mountains (because they are all drawn the same size) extends below the read area, so this area is simply cropped during the reading process (which therefore gives the desired impression of different size mountains.)

    In this one I use an 8x4 cell of 32 reds for the positive, and the remaining 32 reds for the negative.

    Note the expicit command frameRate(1) at the end of my code. I discovered that without this command, Processing would use 100% of one core of my CPU, even though it had finished drawing. As far as I can tell there is no Sleep function, all you can do is reduce the frequency of polling.

    int i,j,x,y,r,g,b,c;
    PImage img;
    color p;
    void setup(){
      size(512,512);
      background(255,255,255);
      frameRate(1000000000);
      noLoop();
     }
    
    void draw() {
      for(i=0; i<40; i++){
        x=round(random(512));
        y=round(random(64,256));
        for(j=-256; j<256; j+=12) line(x,y,x+j,y+256);  
      }
      for(y=0;y<256;y++)for(x=0;x<512;x++){
        r=(x&7)+(y&3)*8;
        b=x>>3;
        g=(255-y)>>2;
        c=brightness(get(x,y))>100? 32:0;
        p=color((r^c)<<2,g<<2,b<<2);
        set(x,y,p);
        p=color((r^32^c)<<2,g<<2,b<<2);
        set(x,511-y,p);  
      }
      save("mountainK.png");
      frameRate(1);
    }
    

    enter image description here

    Because its not full cyan at all. It's (0,217,217). All 32 combinations are present though, just not stretched [0,255]. Edit: You're using steps of 7 but I can't find this in the code. Must be a Processing thing.

    @steveverrill In Processing, you can do `save("filename.png")` to save the current frame buffer to an image. Other image formats are also supported. It'll save you the trouble of taking screenshots. The image is saved to the sketch's folder.

    @Jasonc thanks for the tip, I was sure there must be a way, but I don't think I'll edit these. I left the frame around the images partially to separate them (2 files for such small images was overkill.) I want to do some images in 512x512 (and there's one in particular I have an idea for) so I will upload those in the way you suggest.

    @steveverrill Haha, the Warhols are a nice touch.

    @Zom-B Processing seems to do many things that (annoyingly) aren't mentioned in its documentation: not using the full 256 logical colour channel values in its physical output, blending colours when you don't want, using a full core of my CPU even after it's finished drawing. Still it's simple to get into and you can work around these issues once you know they are there (except the first one, I haven't solved that yet...)

    Something about this color scheme is playing tricks on my eyes. Like my brain is fighting over which color is part of the foreground. I quite like all of these!

    The Marilyns are great...reminiscent of film photos with light-leaking Holga cameras.

License under CC-BY-SA with attribution


Content dated before 6/26/2020 9:53 AM