461
\$\begingroup\$

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

\$\endgroup\$
12
  • 10
    \$\begingroup\$ 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? \$\endgroup\$ Feb 25, 2014 at 22:02
  • 2
    \$\begingroup\$ 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. \$\endgroup\$ Feb 26, 2014 at 6:42
  • 8
    \$\begingroup\$ I actually find your trivial permutation example to be quite pleasing to the eye. \$\endgroup\$
    – Jason C
    Feb 27, 2014 at 0:48
  • 2
    \$\begingroup\$ @Zom-B Man, I freakin' love this post. Thanks! \$\endgroup\$
    – Jason C
    Feb 27, 2014 at 17:26
  • 7
    \$\begingroup\$ Beautiful results/answers! \$\endgroup\$
    – EthanB
    Feb 28, 2014 at 3:37

50 Answers 50

1
2
11
\$\begingroup\$

Java

This was a much better idea. This is some very short Java code; the main method is only 13 lines long:

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;

/**
 *
 * @author Quincunx
 */
public class AllColorImage {

    public static void main(String[] args) {
        BufferedImage img = new BufferedImage(4096, 4096, BufferedImage.TYPE_INT_RGB);

        for (int r = 0; r < 256; r++) {
            for (int g = 0; g < 256; g++) {
                for (int b = 0; b < 256; b++) {
                    img.setRGB(((r & 15) << 8) | g, ((r >>> 4) << 8 ) | b, (((r << 8) | g) << 8) | b);
                }
            }
        }
        try {
             ImageIO.write(img, "png", new File("Filepath"));
        } catch (IOException ex) {
            Logger.getLogger(AllColorImage.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Generates blocks of "color pickers". Basically, in the first block, r=0, in the second, r=1, etc. In each block, g increments with respect to x, and b with respect to y.

I really like bitwise operators. Let me break down the setRGB statement:

img.setRGB(((r & 15) << 8) | g, ((r >>> 4) << 8 ) | b, (((r << 8) | g) << 8) | b);

((r & 15) << 8) | g         is the x-coordinate to be set.
r & 15                      is the same thing as r % 16, because 16 * 256 = 4096
<< 8                        multiplies by 256; this is the distance between each block.
| g                         add the value of g to this.

((r >>> 4) << 8 ) | b       is the y-coordinate to be set.
r >>> 4                     is the same thing as r / 16.
<< 8 ) | b                  multiply by 256 and add b.

(((r << 8) | g) << 8) | b   is the value of the color to be set.
r << 8                      r is 8 bits, so shift it 8 bits to the left so that
| g                         we can add g to it.
<< 8                        shift those left 8 bits again, so that we can
| b                         add b

As a result of the bitwise operators, this takes only 7 seconds to complete. If the r & 15 is replaced with r % 16, it takes 9 seconds.

I chose the 4096 x 4096

Output (screenshot, too big otherwise):

enter image description here

Output with evil grin drawn on it by freehand-red-circles:

enter image description here

\$\endgroup\$
7
  • 2
    \$\begingroup\$ Link to the original so I can verify the validness (count colors) \$\endgroup\$ Feb 26, 2014 at 6:31
  • 2
    \$\begingroup\$ Lol! I forgot I can run Java code. The first image passes, and I can't reproduce the second image (lol)☺ \$\endgroup\$ Feb 26, 2014 at 6:40
  • 16
    \$\begingroup\$ The freehand circles all have the same color, disqualified. :P \$\endgroup\$
    – Nick T
    Feb 27, 2014 at 0:16
  • 3
    \$\begingroup\$ @Quincunx +1 if you can draw a scary face and still keep the color requirements! \$\endgroup\$
    – Jason C
    Feb 27, 2014 at 10:29
  • 2
    \$\begingroup\$ @JasonC See my answer. Credit goes to Quincunx for the inspiration. \$\endgroup\$ Feb 27, 2014 at 21:18
11
\$\begingroup\$

Java

unintentionally inspired by the color chooser solution but in 4096x4096; got some other ideas in pipeline already

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

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author LH
 */
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);
        for (int j = 0; j < 256; j++)//red //255=interesting quirk
        {
            for (int k = 0; k < 16; k++)//blue_MSBs
            {
                for (int l = 0; l < 256; l++)//green 255=interesting quirk
                {
                    for (int m = 0; m < 16; m++)//blue_lsbs
                    {
                        
                        int val = (j<<16)+(k<<12)+(l)+(m<<8);
                        //System.out.println("(idx|j|k|l|m|val)=("+idx+"|"+j+"|"+k+"|"+l+"|"+m+"|"+val+"|)");
                        x[idx]=val;
                        idx++;
                    }
                }
            }
        }
        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.png"));
        ImageIO.write(i, "png", out);
    }
}

Preview (to get full 4096x4096 run the java prog

\$\endgroup\$
10
\$\begingroup\$

Go

Here's my take, using Golang and its default packages to generate a GIF containing all the colors:

package main

import (
    "image"
    "image/color"
    "image/gif"
    "os"
)

func main() {
    var idx uint8
    frames := make([]*image.Paletted, 256)
    delay := make([]int, 256)
    for x := 0; x < 256; x++ {
        pal := make(color.Palette, 129)
        pal[0] = color.RGBA{0, 0, 0, 0}
        idx = 1
        for y := 0; y < 128; y++ {
            pal[idx] = color.RGBA{uint8(x%32) * 8, uint8(y%32) * 8, uint8(x/32+(y/32*8)) * 8, 255}
            idx++
        }
        img := image.NewPaletted(image.Rect(0, 0, 256, 128), pal)
        idx = 1
        for y := 0; y < 128; y++ {
            img.SetColorIndex(x, y, idx)
            idx++
        }
        frames[x] = img
        delay[x] = 1
    }
    jif := gif.GIF{frames, delay, 1}
    file, err := os.Create("EveryColor.gif")
    if err != nil {
        panic(err)
    }
    err = gif.EncodeAll(file, &jif)
    if err != nil {
        panic(err)
    }
    file.Close()

}

And the output:

animated gif containing 2^15 colors

Not pretty like many of the other answers, but it uses the GIF format in an interesting way.

\$\endgroup\$
9
\$\begingroup\$

I used to make these infinite recursive sorters when I was learning to write code, very rewarding to program so little. It will eventually converge around something like below for random colors - but it has the very unique property of continuously walking.

Converges around something like this for random colors.

If you're brave enough to run the 4096x4096 version in your browser with each pixel a different color: god speed to that one poor CPU core handling the JavaScript thread. I am in no way responsible for that whirring fan noise you now hear, or the soon to follow laptop fire.

Source (or here as a gist https://gist.github.com/adrianseeley/9516453):

<canvas id="canvas" width="256", height="256"></canvas>
<script type="text/javascript">
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    var imgdata = ctx.createImageData(canvas.width, canvas.height);
    function set_pixel (x, y, r, g, b) { var index = (x + y * imgdata.width) * 4; imgdata.data[index + 0] = r; imgdata.data[index + 1] = g; imgdata.data[index + 2] = b; imgdata.data[index + 3] = 255; };
    function swap_pixels (x1, y1, x2, y2) { var p1 = get_pixel(x1, y1); var p2 = get_pixel(x2, y2); set_pixel(x1, y1, p2[0], p2[1], p2[2]); set_pixel(x2, y2, p1[0], p1[1], p1[2]); };
    function get_pixel (x, y) { var index = (x + y * imgdata.width) * 4; return [imgdata.data[index + 0], imgdata.data[index + 1], imgdata.data[index + 2]]; };
    function draw () { ctx.putImageData(imgdata, 0, 0); };
    function prep () {
        // meant for 4096x4096 canvas, slow as fuck
        var r = 0;
        var g = 0;
        var b = 0;
        for (var x = 0; x < canvas.width; x++) {
            for (var y = 0; y < canvas.height; y++) {
                set_pixel(x, y, r, g, b);
                b++;
                if (b > 255) {
                    b = 0;
                    g++;
                    if (g > 255) {
                        g = 0;
                        r++;
                    }
                }
            }
        }
        draw();
    };
    function preprandom () {
        // way prettier
        for (var x = 0; x < canvas.width; x++) for (var y = 0; y < canvas.height; y++) set_pixel(x, y, Math.floor(Math.random() * 255), Math.floor(Math.random() * 255), Math.floor(Math.random() * 255));
        draw();
    };
    function multi_pass_sort (lambda_x, lambda_y) {
        var sorted = true;
        for (var x = 1; x < canvas.width; x++) for (var y = 0; y < canvas.height; y++) if (lambda_x(get_pixel(x - 1, y), get_pixel(x, y))) { swap_pixels(x - 1, y, x, y); sorted = false; }
        for (var x = 0; x < canvas.width; x++) for (var y = 1; y < canvas.height; y++) if (lambda_y(get_pixel(x, y - 1), get_pixel(x, y))) { swap_pixels(x, y - 1, x, y); sorted = false; }
        draw();
        if (!sorted) setTimeout(function () { multi_pass_sort(lambda_x, lambda_y); }, 0);
        else console.log('done!');
    };
    //prep();
    preprandom();
    setTimeout(function () { multi_pass_sort(function lambda_x (a, b) { return a[0] > b[0] || a[1] > b[1]; }, function lambda_y (a, b) { return a[1] > b[1] || a[2] > b[2]; });
    }, 1000);
</script>

Try experimenting with the lambda_x and lambda_y functions and enjoy your exploration of the multidimensional!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Haha, plus one for "laptop fire"! Welcome to the site. \$\endgroup\$ Mar 19, 2014 at 15:53
  • \$\begingroup\$ Can't find the run link. \$\endgroup\$ Jun 30, 2017 at 19:25
8
\$\begingroup\$

I was inspired by my answer from another popularity contest question which used iterative depth-first search to generate a maze. I used a variation of the algorithm used in a couple other answers that determines the best color to place at a certain pixel given the colors occupying the neighboring pixels. The algorithm starts at the center pixel and works its way visiting every pixel in the image in a random path.

The code is set up so that one can change the fields to give different results, such as adjusting the width and height of the image (and all relevant colors are used depending on the dimensions), random seed, and whether to shuffle or sort the colors before placing them from the list onto the image. Sorting is done using the HSV color model.

The resulting image will appear in a window once the image generation has finished. Scroll panes may be added for convenience in larger images. Assuming searchLimit = 8, on my machine it takes seconds to produce 256x128 and 512x512 images, around ten minutes for 2048x1024 images, and half a day or longer for 4096x4096 images.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.function.BinaryOperator;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public class RGBImage {
    /**
     * Width and height of the image. Choose either of:
     * 128x256  256x128  512x512  1024x2048  2048x1024  4096x4096
     **/
    private final int width = 512, height = 512;

    /**
     * Number of colors from the front of the list to compare when searching
     * for the best color. Note: the higher this value, the longer it will take
     * to produce the image.
     **/
    private final int searchLimit = 8;

    /**
     * Seed for the color list shuffling and depth-first search algorithm.
     * Using the same seed will yield the same image provided no other
     * algorithm settings were altered. Add 'L' or 'l' after the value; e.g. 1L
     * Using 'null' will create a random image every time the program is run.
     **/
    private final Long seed = null;
    private final Random rand = new Random(seed == null ? System.nanoTime() : seed);

    /**
     * List of all colors.
     **/
    private List<Integer> colors;

    /**
     * Comparator to sort the colors. Use any of
     * null  byHue()  bySaturation()  byValue()  byRed()  byGreen()  byBlue()
     *
     * Using 'null' causes the colors to be sorted in ascending order by their
     * numerical value.
     * You can sort by one method first and then by another; e.g.
     * byHue().thenComparing(bySaturation());
     **/
    private final Comparator<Integer> comparator = null;

    /**
     * Flags for shuffling and sorting the values in the list.
     * If both flags are 'false', the colors in the list will be sorted in
     * ascending order by their numerical value.
     * If both flags are 'true', shuffling will occur first, then sorting. This
     * may give interesting results!
     **/
    private final boolean shuffle = false, sort = false;

    /**
     * The name to save the image as. The extension is "png" and should not be
     * included.
     **/
    private final String fileName = "output";

    public RGBImage() {
        init();

        int[] pixels = new int[width * height];
        Arrays.fill(pixels, -1);
        constructArray(pixels, new short[]{(short) (width/2), (short) (height/2)});

        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        img.setRGB(0, 0, width, height, pixels, 0, width);

        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame();
            JPanel panel = new JPanel() {
                @Override
                public Dimension getPreferredSize() {
                    return new Dimension(width, height);
                }

                @Override
                protected void paintComponent(Graphics g) {
                    super.paintComponent(g);
                    g.drawImage(img, 0, 0, this);
                }
            };
            frame.add(new JScrollPane(panel), BorderLayout.CENTER);

            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            if (width <= 512)
                frame.pack();
            else
                frame.setSize(750, 750);
            frame.setVisible(true);
        });

        try {
            ImageIO.write(img, "png", new File(fileName + ".png"));
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private void init() {
        colors = new ArrayList<>(width * height);
        int bitsPerComp = (int) (Math.log(width * height) / Math.log(2)) / 3;
        int pow2 = 1 << bitsPerComp, fact256 = 256 / pow2;

        float adj = 255f / (256 - fact256);
        for (int k = 0; k < width * height; k++) {
            int r = (int) (((k >> (bitsPerComp * 2)) % pow2) * fact256 * adj) << 16,
                g = (int) (((k >> bitsPerComp) % pow2) * fact256 * adj) << 8,
                b = (int) ((k % pow2) * fact256 * adj);
            colors.add(r | g | b);
        }
        if (shuffle)
            Collections.shuffle(colors);
        if (sort)
            Collections.sort(colors, comparator);
    }

    private void constructArray(int[] pixels, short[] coord) {
        Deque<short[]> path = new LinkedList<>();
        path.add(coord);
        boolean up, right, down, left;
        while (true) {
            coord = path.peekFirst();
            int col = bestColor(pixels, coord);
            pixels[coord[1] * width + coord[0]] = col;
            colors.remove(Integer.valueOf(col));

            if (isSurrounded(pixels, coord)) {
                do {
                    coord = path.removeFirst();
                } while (isSurrounded(pixels, coord) && !path.isEmpty());
            }

            if (path.isEmpty())
                break;

            up = coord[1] != 0 && pixels[(coord[1] - 1) * width + coord[0]] == -1;
            right = coord[0] != width - 1 && pixels[coord[1] * width + coord[0] + 1] == -1;
            down = coord[1] != height - 1 && pixels[(coord[1] + 1) * width + coord[0]] == -1;
            left = coord[0] != 0 && pixels[coord[1] * width + coord[0] - 1] == -1;

            byte size = 0;
            if (up) size++;
            if (right) size++;
            if (down) size++;
            if (left) size++;

            byte[] possible = new byte[size];
            if (up) possible[--size] = 0;
            if (right) possible[--size] = 1;
            if (down) possible[--size] = 2;
            if (left) possible[--size] = 3;

            switch (possible[rand.nextInt(possible.length)]) {
                case 0: path.addFirst(new short[] {coord[0], (short) (coord[1] - 1)}); break;
                case 1: path.addFirst(new short[] {(short) (coord[0] + 1), coord[1]}); break;
                case 2: path.addFirst(new short[] {coord[0], (short) (coord[1] + 1)}); break;
                case 3: path.addFirst(new short[] {(short) (coord[0] - 1), coord[1]}); break;
            }
        }
    }

    private boolean isSurrounded(int[] pixels, short[] c) {
        return (c[1] == 0 ? true : pixels[(c[1] - 1) * width + c[0]] != -1) &&
               (c[0] == width - 1 ? true : pixels[c[1] * width + c[0] + 1] != -1) &&
               (c[1] == height - 1 ? true : pixels[(c[1] + 1) * width + c[0]] != -1) &&
               (c[0] == 0 ? true : pixels[c[1] * width + c[0] - 1] != -1);
    }

    private int bestColor(int[] pixels, short[] coord) {
        return colors
                .subList(0, Math.min(colors.size(), searchLimit))
                .parallelStream()
                .reduce(BinaryOperator.minBy(
                    Comparator.comparingDouble(col ->
                        Arrays.stream(getNeighbors(coord))
                            .filter(s -> s != null)
                            .mapToInt(s -> pixels[s[1] * width + s[0]])
                            .filter(i -> i != -1)
                            .mapToDouble(i -> HSVdiff(col, i))
                            .sum()
                    )
                ))
                .orElseThrow(() -> new RuntimeException("No best color found"));
    }

    private short[][] getNeighbors(short[] arr) {
        short[][] res = new short[8][2];
        byte index = 0;
        for (; index < 8; index++)
            res[index] = null;
        index = 0;
        for (short cx = (short) (arr[0] - 1); cx <= arr[0] + 1; cx++) {
            if (cx < 0 || cx == width) continue;
            for (short cy = (short) (arr[1] - 1); cy <= arr[1] + 1; cy++) {
                if (cy < 0 || cy == height) continue;
                if (cx == arr[0] && cy == arr[1]) continue;
                res[index++] = new short[] {cx, cy};
            }
        }
        return res;
    }

    private double HSVdiff(int c1, int c2) {
        float[] hsv1 = Color.RGBtoHSB(c1 >> 16, (c1 >> 8) & 0xff, c1 * 0xff, null),
                hsv2 = Color.RGBtoHSB(c2 >> 16, (c2 >> 8) & 0xff, c2 * 0xff, null);
        return Math.pow(hsv2[0] - hsv1[0], 2) + Math.pow(hsv2[1] - hsv1[1], 2) + Math.pow(hsv2[2] - hsv1[2], 2);
    }

    private Comparator<Integer> byHue() {
        return Comparator.comparingDouble(col -> Color.RGBtoHSB(col >> 16, (col >> 8) & 0xff, col & 0xff, null)[0]);
    }

    private Comparator<Integer> bySaturation() {
        return Comparator.comparingDouble(col -> Color.RGBtoHSB(col >> 16, (col >> 8) & 0xff, col & 0xff, null)[1]);
    }

    private Comparator<Integer> byValue() {
        return Comparator.comparingDouble(col -> Color.RGBtoHSB(col >> 16, (col >> 8) & 0xff, col & 0xff, null)[2]);
    }

    private Comparator<Integer> byRed() {
        return Comparator.comparingInt(col -> col >> 16);
    }

    private Comparator<Integer> byGreen() {
        return Comparator.comparingInt(col -> (col >> 8) & 0xff);
    }

    private Comparator<Integer> byBlue() {
        return Comparator.comparingInt(col -> col & 0xff);
    }

    public static void main(String[] args) {
        new RGBImage();
    }
}

Here's a random 512x512 image, unsorted (colors are stored in the list in ascending numerical order), searchLimit = 8:

512x512_search8

The next three are 512x512, use seed = 24680L, and shuffled the colors in the list without sorting them afterward. The searchLimit was the only thing that changed between them, producing interesting results.

searchLimit = 8:

512x512_search8_seed24680_shuffle

searchLimit = 128:

512x512_search128_seed24680_shuffle

searchLimit = 2048:

512x512_search2048_seed24680_shuffle

Some 256x128 images, seed = 24680, searchLimit = 64, the first sorted by hue, the second sorted by saturation, the third sorted by value, and the fourth shuffled and then sorted by value:

256x128_search64_seed24680_sortHue 256x128_search64_seed24680_sortSat 256x128_search64_seed24680_sortVal 256x128_search64_seed24680_shuffle_sortVal

Linked are 2048x1024 images, with seed = 24680 and the colors sorted by hue. Some interesting differences arise when modifying the searchLimit again. The ones used here are searchLimit = 8 and searchLimit = 512.

And last, but certainly not least, linked is a random 4096x4096 image with searchLimit = 8 and sorted by hue.

\$\endgroup\$
7
\$\begingroup\$

BASH with *nix commands

Some oneliners generating PPM format pictures

Ok... this sure will not be the most popular solution, but I think it is worth being mentioned because it shows (one way) how to build raster graphics with shell commands:

{ printf 'P3\n# x.ppm\n256 128\n31\n' ; printf '%s\n' {0..31}.{0..31}.{0..31} | tr . ' ' ; } >x.ppm

Output:

x

You can add other commands to that pipe to shuffle or rearrange the pixels... let's try 'sort':

{ printf 'P3\n# x.ppm\n256 128\n31\n' ; printf '%s\n' {0..31}.{0..31}.{0..31} | sort | tr . ' ' ; } >y.ppm

y

Ok... that was boring!

How about some random?

{ printf 'P3\n# x.ppm\n256 128\n31\n' ; printf '%s\n' ${RANDOM}.{0..31}.{0..31}.{0..31} | sort | awk -F. '{ print $2" "$3" "$4}' ; } >z.ppm

z

If you think this is fun, read about the PBM, PGM and PPM picture file formats.

They may look like a fossil but who says that this means "no fun!"?

\$\endgroup\$
2
  • \$\begingroup\$ It's no fossil if they have practical use, namely plain text format (and not BASE64 encoded or stuff) \$\endgroup\$ Mar 21, 2014 at 11:44
  • \$\begingroup\$ "They may look like a fossil" is not "they are a fossil". Maybe I should have been more verbose to be understood faster... \$\endgroup\$
    – user19214
    Mar 21, 2014 at 15:15
6
\$\begingroup\$

Python

Each run produces one of ~1.3 trillion possible outputs. Requires ImageMagick's display command.

#!/usr/bin/env python
from itertools import product
from math import log
from subprocess import Popen, PIPE
from random import shuffle

w = 256
h = 128
entropy = int(log(w * h, 2))
colors = 3
byte = 256
color_range = xrange(0, byte, byte / 2**(entropy/colors))
output = bytearray(w * h * colors)
bit_permutation = range(entropy)
shuffle(bit_permutation)

def shuffle_bits(n):
    shuffled = 0
    for b, bit in zip(bit_permutation, bin(n)[2:].zfill(entropy)):
        if bit == '1': shuffled += 1 << b
    return shuffled

for channels, pos in zip(product(color_range, repeat=colors),
                         map(shuffle_bits, xrange(w * h))):
    output[colors*pos:colors*(pos+1)] = ''.join(chr(k) for k in channels)

proc = Popen(
    ['display', '-size', '%sx%s' % (w, h), '-depth', '8', 'rgb:-'], stdin=PIPE)
proc.communicate(str(output))

Some outputs:

output example 1
output example 2
output example 3

Try changing w / h to 512 / 512 or 2048 / 1024 for more colors.

\$\endgroup\$
1
  • 5
    \$\begingroup\$ 1.3 trillion frames at 30 FPS = 1373 years. Let's get that video started! \$\endgroup\$
    – Jason C
    Mar 1, 2014 at 2:51
6
\$\begingroup\$

Java, 24-bit (4096 by 4096)

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.swing.JFrame;


public class ALLTHECOLORS{
    static class ImageComponent extends Component{
        private static final long serialVersionUID = 1L;
        private BufferedImage i;
        public ImageComponent(BufferedImage i){
            this.i=i;
        }
        public int width(){
            return 512;
        }
        public int height(){
            return 512;
        }
        public void paint(Graphics g){
            g.drawImage(i, 0, 0,width(),height(),null);
        }
    }
    static class ColorIterator implements Iterator<Color>{
        int r=0,g=0,b=0;
        public boolean hasNext() {
            return r<256;
        }
        public Color next(){
            Color c=new Color(255-r,255-g,255-b);
            b++;
            if(b>255){
                b=0;
                g++;
            }
            if(g>255){
                g=0;
                r++;
            }
            return c;
        }   
    }
    static double d(Color a,Color b){
        int dr=a.getRed()-b.getRed();
        int dg=a.getGreen()-b.getGreen();
        int db=a.getBlue()-b.getBlue();
        return Math.sqrt(dr*dr+dg*dg+db*db);
    }
    static int hue(Color c){
        return (int)(0.5+90*Color.RGBtoHSB(c.getRed(),c.getGreen(),c.getBlue(),null)[0]);
    }
    static int sat(Color c){
        return (int)(0.5+7*Color.RGBtoHSB(c.getRed(),c.getGreen(),c.getBlue(),null)[1]);
    }
    public static void main(String[]args) throws IOException{
        Random rand=new Random(System.currentTimeMillis()%1000);
        BufferedImage img=new BufferedImage(4096,4096,BufferedImage.TYPE_INT_RGB);
        Iterator<Color>it=new ColorIterator();
        List<Color>col=new ArrayList<>(256*256*256);
        while(it.hasNext())col.add(it.next());
        for(int i=0;i<col.size();i++){
            col.set(i,col.set(rand.nextInt(i+1),col.get(i)));
        }
        col.sort((a,b)->{
            int dels=sat(a)-sat(b);
            int delh=hue(a)-hue(b);
            return dels!=0?dels:delh;
        });
        it=col.iterator();
        for(int x=0;x<4096;x++){
            for(int y=0;y<4096;y++){
                img.setRGB(x, y,it.next().getRGB());
            }
        }
        ImageIO.write(img,"jpg",new File("the_colors.jpg"));
        JFrame frame=new JFrame();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        ImageComponent c=new ImageComponent(img);
        frame.add(c);
        frame.setSize(new Dimension(c.width(),c.height()));
        frame.setVisible(true);
    }
}

It saves the full 4096 by 4096 version to the file "the_colors.jpg" and then draws a 1/8-size preview onto a JFrame. It typically takes 35-40 seconds to generate the result.

This is a fairly representative result (the 1/8-size preview is shown here, use the link to view at full size)

enter image description here

And here's a 10x close-up of a 25-by-25 portion of one of the blue-green sections: enter image description here

\$\endgroup\$
6
\$\begingroup\$

Javascript (Browser)

I recommend opening a blank page (about:blank), then pasting this into the console. It clears the page and creates its own canvas to draw on. You can watch it drawing live, which takes around 30 seconds on my chromebook.

(async function() {
    var canvas = document.createElement("canvas");

    canvas.width = 256;
    canvas.height = 128;
    canvas.style.imageRendering = "pixelated";

    while (document.body.firstChild)
        document.body.removeChild(document.body.firstChild);
    document.body.appendChild(canvas);

    var ctx = canvas.getContext("2d");

    var data = ctx.createImageData(256, 128);

    var colors = new Int16Array(32768);
    
    for (let i = 0; i < 32768; i++)
        colors[i] = i;
    
    for (let j, i = 32767; i >= 0; i--) {
        j = Math.random() * (i + 1) | 0;
        [colors[i], colors[j]] = [colors[j], colors[i]];
    }
    
    var unused = [...colors];
    
    var pixels = new Int16Array(32768).fill(-1);
    
    var find_color = function(color_one, color_two = -1) {
        var color_dist = function(from, to) {
            var red = Math.abs(((from & 31744) >> 10) - ((to & 31744) >> 10));
            var green = Math.abs(((from & 992) >> 5) - ((to & 992) >> 5));
            var blue = Math.abs((from & 31) - (to & 31));

            return red + green + blue;
        };
        
        var max = [0, Infinity];
        
        for (let d, i = 0; i < unused.length; i++) {
            d = color_dist(color_one, unused[i]) + (color_two != -1 ? color_dist(color_two, unused[i]) : 0) + (Math.random() * 12 | 0);

            if (d <= max[1])
                max = [i, d];
        }

        var color = unused[max[0]];
        
        unused = unused.filter(u => u != color);
        
        return color;
    };
    
    var to_hex = function(not_hex) {
        var red = ((not_hex & 31744) >> 7).toString(16).padStart(2, "0");
        var green = ((not_hex & 992) >> 2).toString(16).padStart(2, "0");
        var blue = ((not_hex & 31) << 3).toString(16).padStart(2, "0");
        
        return "#" + red + green + blue;
    };
    
    ctx.fillStyle = to_hex(pixels[0] = find_color(Math.random() * 32768 | 0));
    ctx.fillRect(0, 0, 1, 1);
    
    for (let i = 1; i < 128; i++) {
        ctx.fillStyle = to_hex(pixels[i * 256] = find_color(pixels[(i - 1) * 256]));
        ctx.fillRect(0, i, 1, 1);
    }
    
    await new Promise(resolve => setTimeout(resolve, 0));
    
    for (let i = 1; i < 256; i++) {
        ctx.fillStyle = to_hex(pixels[i] = find_color(pixels[i - 1]));
        ctx.fillRect(i, 0, 1, 1);
        
        for (let j = 1; j < 128; j++) {
            ctx.fillStyle = to_hex(pixels[j * 256 + i] = find_color(pixels[j * 256 + (i - 1)], pixels[(j - 1) * 256 + i - 1]));
            ctx.fillRect(i, j, 1, 1);
        }
        
        await new Promise(resolve => setTimeout(resolve, 0));
    }
})();

This will pick a random color for the top left pixel, and move down the image picking the nearest color (with a bit of random offset in the nearest color picker). It will then jump one pixel to the right, and back to the top of the screen. It will always choose according to the pixels both above and to the left.

Some outputs:

Looks like the masterwork of a renaissance painter who likes diagonal lines

Looks like the painter lost his inspiration and painted the same thing again

Looks like the painter sold out and just started making a bunch of the same thing because he doesn't make much money, but is fueled by a capitalistic desire for wealth which may be caused by his insecurity and desire to fit the societal norms imposed by faceless corporate advertising

If I make it only choose colors based on those to the left of each pixel, the result gets a bit more interesting:

Looks like a digital file containing an image of the painter's work was recovered from a corrupted disk

Looks like someone wanted to show off the painter's work on a CRT monitor, but forgot how it works and jammed wires from an exposed mains line into a VGA port

However, the most fascinating result I got was by making each pixel's color reliant only on the one on top of it; I expected vertical stripes, but instead got very thin diagonal ones, similar to the original:

Turns out it was looking only at the top left. Still looks pretty cool:

Looks like someone tried to recreate the painter's work in photoshop, but set the brush size too small

Looks like the painter tried recreating his masterpiece in colored pencil, to great success

Looks like the painter has found peace with who he is, and settled down to make colored pencil drawings and use the profit for charity

Later on, I've tried making it so that every other column is draw bottom to top instead, which results in these neat looking sideways stripes:

Looks like the painter started making horizontal stripes in the same style as the original diagonal ones

Looks like the painter's brush needs to be cleaned, because the edges of the stripes are visibly toothed as a result of the direction swapping every other column

Looks like the painter lost interest in the last 10% of the project, because a bunch of random colors are remaining that don't form nice looking stripes

If you base each pixel off of the one to the left, the one to the top, and the one halfway to the left edge, you get much more separated stripes:

Looks like the painter switched to a smaller, higher quality brush, but it made it look uglier anyway

Looks almost like one of those striped beach towels

If you mostly base each pixel off of the ones at the far top and left of it (with some influence from its top left pixel), you get this interesting pattern:

Looks like nicely blended watercolors. Until you move right. Then it looks like a modern art display from the 19somethings

Looks like a tie dye shirt, after a five year old hits every button and slider in photoshop (but only on the right)

My new favorite: When you base each pixel off of the three to the left (above, directly to the left, and below), you get these:

Looks like it was actually meant to be drawn sideways, unlike the previous sideways ones that all look like low quality rotations of the originals

Looks like a nice oil painting, screenshotted and printed with a cheap dot matrix printer that's running out of ink

Looks like someone else printed out that last one and got something that looks similar but isn't the same

If anyone comes up with other cool ways to modify this code, let me know!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ It doesn't make sense to see correlation to the pixel to the top-left when you only depend on the pixel above. Sure enough, when I try it I get vertical stripes. jsfiddle.net/wnm20sa1 \$\endgroup\$ Sep 10, 2020 at 7:59
  • \$\begingroup\$ @MarkJeronimus Ah, that's probably what I did wrong! Later today I'm adding some more variations, and I'll make sure to fix that. \$\endgroup\$ Sep 10, 2020 at 12:27
4
\$\begingroup\$

Processing

Uses the same algorithm as the given example, but gives an animated output! Isn't it fun just to stare at your computer screen as the pixels slowly fill up one by one?

void setup(){
  size(256,128);
  background(0);
  frameRate(2400); //adjust as needed
}
int i=0;
void draw() {
  stroke(i<<3&255,i>>2&255,i>>7&255);
  point(i&255,i/256);
  i++;
}

See it run online here.

Here's a screenshot at the end of the animation:

enter image description here

\$\endgroup\$
1
  • \$\begingroup\$ You should change the point call to point(i&255,floor(i/256)); This will stop the second half being shifted down by one pixel with processing.js \$\endgroup\$ Jun 9, 2014 at 2:31
4
\$\begingroup\$

Javascript (jQuery), HTML and CSS

Result

Javascript will create a table containing one cell for each color. jQuery is used for styling table cells.

$(function() {
    $container = $('body > main > table#container > tbody');

    $td = $('<td>0</td>');

    var reds = [];
    var greens = [];
    var blues = [];

    for (var i = 0; i <= 256; i = i + 8)
    {
        reds.push(i);
        greens.push(i);
        blues.push(i);
    }

    var y_len = (reds.length * 4);
    var x_len = greens.length;
    var z_len = blues.length;

    var z = 0;
    var z_end = 0;

    for (var y = 0; y < y_len; y++)
    {
        $row = $('<tr></tr>');

        tmp_z = ((y % 4) * 8);
        tmp_z_end = (tmp_z + 8);

        for (var x = 0; x < x_len; x++)
        {
            for (var z = tmp_z; z < tmp_z_end; z++)
            {
                $pixel = $td
                    .clone()
                    .css({ 'background-color':
                        'rgb(' + reds[x] +','
                            + greens[Math.floor(y / 4)] + ','
                            + blues[z] + ')' })
                ;

                $row.append($pixel);
            }
        }

        $container.append($row, "\n");
    }
});

Result (warning: your browser will take some time to display the result)

Source

\$\endgroup\$
4
\$\begingroup\$

Java 8

The program attempts to color a Mandelbrot using every color. Naturally, this ensures that there are two parts of the program that take a long time: the color generation, and the Mandelbrot generation.

For each pixel of the image, I compute whether it is in or out of the Mandelbrot, before estimating the exterior difference for each exterior point. The interior distances are currently all set to 0. At this point, we sort according to the distance to the Mandelbrot Set, then grab the (sorted) color at the same position in the sort as the pixel.

To sort the color, I compute the hue and saturation according to these formulae. I sort by saturation, with hue coming in afterwards.

package mandelbrot;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.List;

public class Mandelbrot {
    public static final int WIDTH = 4096;
    public static final int HEIGHT = 4096;
    public static final int N = 1000; // Number of iterations
    public static final double R = 2; // Bailout radius
    public static final double MIN_X = -2.2;
    public static final double MAX_X = 1.4;
    public static final double MIN_Y = -1.2;
    public static final double MAX_Y = 1.2;

    private static double discreteToReal(int value, int maxValue, double min, double max) {
        return ((double) value) / maxValue * (max - min) + min;
    }

    private static boolean isDefinitelyInside(double x, double y) {
        double temp = x - 0.25;
        double q = temp * temp + y * y;
        return q * (q + (x - 0.25)) < 0.25 * y * y;
    }

    private static boolean isDefinitelyOutside(double x, double y) {
        if (!(-2 <= x && x <= 0.7)) return true;
        if (!(-1.2 <= y && y <= 1.2)) return true;
        if (x * x + y * y < 2 * 2) return true;
        return false;
    }

    private static boolean isInsideMandelbrot(double x0, double y0) {
        if (isDefinitelyInside(x0, y0)) return true;
        if (isDefinitelyOutside(x0, y0)) return false;

        double x = 0.0;
        double y = 0.0;
        int numIterations = 0;
        while (x * x + y * y < R * R && numIterations < N) {
            double xtemp = x * x - y * y + x0;
            y = 2 * x * y + y0;
            x = xtemp;
            numIterations++;
        }

        return x * x + y * y < R * R;
    }

    private static double distanceToMandelbrot(int px, int py) {
        double x0 = discreteToReal(px, WIDTH, MIN_X, MAX_X);
        double y0 = discreteToReal(py, WIDTH, MIN_Y, MAX_Y);

        if (isInsideMandelbrot(x0, y0)) return interiorDistanceEstimate(x0, y0);
        return exteriorDistanceEstimate(x0, y0);
    }

    private static double exteriorDistanceEstimate(double x0, double y0) {
        double pn_x = x0;
        double pn_y = y0;
        double derivative_pn = 1;

        for (int i = 0; i < N; i++) {
            double next_pn_x = pn_x * pn_x - pn_y * pn_y + x0;
            double next_pn_y = 2 * pn_x * pn_y + y0;
            double next_d_pn = 2 * pn_x * derivative_pn + 1;
            pn_x = next_pn_x;
            pn_y = next_pn_y;
            derivative_pn = next_d_pn;
        }

        double temp = Math.hypot(pn_x, pn_y);
        return 2 * temp * Math.log(temp) / Math.abs(derivative_pn);
    }

    private static double interiorDistanceEstimate(double x0, double y0) {
        return 0;
    }

    private static BufferedImage computeMandelbrot(List<Color> colors) {
        double[][] distances = new double[HEIGHT][WIDTH];

        double start = System.currentTimeMillis() * 1e-3;
        System.out.println("\tGenerating points...");

        List<Point> points = new ArrayList<>(WIDTH * HEIGHT);
        for (int x = 0; x < WIDTH; x++) {
            for (int y = 0; y < HEIGHT; y++) {
                points.add(new Point(x, y));
            }
        }
        Collections.shuffle(points, new Random(133));

        System.out.printf("\tElapsed time: %f%n", 1e-3 * System.currentTimeMillis() - start);
        start = 1e-3 * System.currentTimeMillis();
        System.out.println("\tCalculating Mandelbrot...");

        points.parallelStream()
                .forEach(xy -> {
                    int x = xy.x;
                    int y = xy.y;
                    distances[y][x] = distanceToMandelbrot(x, y);
                });

        System.out.printf("\tElapsed time: %f%n", 1e-3 * System.currentTimeMillis() - start);
        start = 1e-3 * System.currentTimeMillis();
        System.out.println("\tAttempting to match with colors...");

        Collections.sort(points, (a, b) -> {
            double bDistance = distances[b.y][b.x];
            double aDistance = distances[a.y][a.x];
            return Double.compare(aDistance, bDistance);
        });

        System.out.printf("\tElapsed time: %f%n", 1e-3 * System.currentTimeMillis() - start);
        start = 1e-3 * System.currentTimeMillis();
        System.out.println("\tConstructing image...");

        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);

        Iterator<Color> colorIterator = colors.iterator();
        for (Point xy : points) {
            image.setRGB(xy.x, xy.y, colorIterator.next().getRGB());
        }

        System.out.printf("\tElapsed time: %f%n", 1e-3 * System.currentTimeMillis() - start);

        return image;
    }

    public static void main(String[] args) throws IOException {
        double start = System.currentTimeMillis() * 1e-3;
        double veryBeginning = start;
        System.out.println("Generating colors...");
        List<Color> colors = new ArrayList<>(WIDTH * HEIGHT);

        for (int rgb = 0; rgb < WIDTH * HEIGHT; rgb++) {
            colors.add(new Color(rgb));
        }
        Collections.shuffle(colors, new Random(0));

        System.out.printf("Elapsed time: %f%n", 1e-3 * System.currentTimeMillis() - start);
        start = 1e-3 * System.currentTimeMillis();
        System.out.println("Sorting colors...");

        colors.sort((a, b) -> {
            BigDecimal r1 = BigDecimal.valueOf(a.getRed(), 100).divide(BigDecimal.valueOf(255), RoundingMode.HALF_DOWN);
            BigDecimal g1 = BigDecimal.valueOf(a.getGreen(), 100).divide(BigDecimal.valueOf(255), RoundingMode.HALF_DOWN);
            BigDecimal b1 = BigDecimal.valueOf(a.getBlue(), 100).divide(BigDecimal.valueOf(255), RoundingMode.HALF_DOWN);

            BigDecimal r2 = BigDecimal.valueOf(b.getRed(), 100).divide(BigDecimal.valueOf(255), RoundingMode.HALF_DOWN);
            BigDecimal g2 = BigDecimal.valueOf(b.getGreen(), 100).divide(BigDecimal.valueOf(255), RoundingMode.HALF_DOWN);
            BigDecimal b2 = BigDecimal.valueOf(b.getBlue(), 100).divide(BigDecimal.valueOf(255), RoundingMode.HALF_DOWN);

            BigDecimal cmax1 = r1.max(g1).max(b1);
            BigDecimal cmin1 = r1.min(g1).min(b1);

            BigDecimal cmax2 = r2.max(g2).max(b2);
            BigDecimal cmin2 = r2.min(g2).min(b2);

            BigDecimal delta1 = cmax1.subtract(cmin1);
            BigDecimal delta2 = cmax2.subtract(cmin2);

            BigDecimal h1 = delta1.compareTo(BigDecimal.ZERO) == 0 ?
                    BigDecimal.ZERO
                    : cmax1.compareTo(r1) == 0 ?
                    BigDecimal.valueOf(60).multiply(g1.subtract(b1).divide(delta1, RoundingMode.HALF_DOWN))
                    : cmax1.compareTo(g1) == 0 ?
                    BigDecimal.valueOf(60).multiply(b1.subtract(r1).divide(delta1, RoundingMode.HALF_DOWN).add(BigDecimal.valueOf(2)))
                    : BigDecimal.valueOf(60).multiply(r1.subtract(g1).divide(delta1, RoundingMode.HALF_DOWN)).add(BigDecimal.valueOf(4));
            if (h1.compareTo(BigDecimal.ZERO) < 0) {
                h1 = h1.add(BigDecimal.valueOf(360));
            }

            BigDecimal h2 = delta2.compareTo(BigDecimal.ZERO) == 0 ?
                    BigDecimal.ZERO
                    : cmax2.compareTo(r2) == 0 ?
                    BigDecimal.valueOf(60).multiply(g2.subtract(b2).divide(delta2, RoundingMode.HALF_DOWN))
                    : cmax2.compareTo(g2) == 0 ?
                    BigDecimal.valueOf(60).multiply(b2.subtract(r2).divide(delta2, RoundingMode.HALF_DOWN).add(BigDecimal.valueOf(2)))
                    : BigDecimal.valueOf(60).multiply(r2.subtract(g2).divide(delta2, RoundingMode.HALF_DOWN)).add(BigDecimal.valueOf(4));
            if (h2.compareTo(BigDecimal.ZERO) < 0) {
                h2 = h2.add(BigDecimal.valueOf(360));
            }

            return h1.compareTo(h2);
        });

        colors.sort((a, b) -> {
            BigDecimal r1 = BigDecimal.valueOf(a.getRed(), 100).divide(BigDecimal.valueOf(255), RoundingMode.HALF_DOWN);
            BigDecimal g1 = BigDecimal.valueOf(a.getGreen(), 100).divide(BigDecimal.valueOf(255), RoundingMode.HALF_DOWN);
            BigDecimal b1 = BigDecimal.valueOf(a.getBlue(), 100).divide(BigDecimal.valueOf(255), RoundingMode.HALF_DOWN);

            BigDecimal r2 = BigDecimal.valueOf(b.getRed(), 100).divide(BigDecimal.valueOf(255), RoundingMode.HALF_DOWN);
            BigDecimal g2 = BigDecimal.valueOf(b.getGreen(), 100).divide(BigDecimal.valueOf(255), RoundingMode.HALF_DOWN);
            BigDecimal b2 = BigDecimal.valueOf(b.getBlue(), 100).divide(BigDecimal.valueOf(255), RoundingMode.HALF_DOWN);

            BigDecimal cmax1 = r1.max(g1).max(b1);
            BigDecimal cmin1 = r1.min(g1).min(b1);

            BigDecimal cmax2 = r2.max(g2).max(b2);
            BigDecimal cmin2 = r2.min(g2).min(b2);

            BigDecimal delta1 = cmax1.subtract(cmin1);
            BigDecimal delta2 = cmax2.subtract(cmin2);

            BigDecimal l1 = cmax1.add(cmin1).divide(BigDecimal.valueOf(2), RoundingMode.HALF_DOWN);
            BigDecimal l2 = cmax2.add(cmax2).divide(BigDecimal.valueOf(2), RoundingMode.HALF_DOWN);

            BigDecimal divisor1 = BigDecimal.ONE.subtract(l1.subtract(BigDecimal.ONE).abs());
            BigDecimal s1 = delta1.compareTo(BigDecimal.ZERO) == 0 || divisor1.compareTo(BigDecimal.ZERO) == 0 ?
                    BigDecimal.ZERO
                    : delta1.divide(divisor1, RoundingMode.HALF_DOWN);

            BigDecimal divisor2 = BigDecimal.ONE.subtract(l2.subtract(BigDecimal.ONE).abs());
            BigDecimal s2 = delta2.compareTo(BigDecimal.ZERO) == 0 || divisor2.compareTo(BigDecimal.ZERO) == 0 ?
                    BigDecimal.ZERO
                    : delta2.divide(divisor2, RoundingMode.HALF_DOWN);

            return s1.compareTo(s2);
        });

        System.out.printf("Elapsed time: %f%n", 1e-3 * System.currentTimeMillis() - start);
        start = 1e-3 * System.currentTimeMillis();
        System.out.println("Generating Mandelbrot...");
        BufferedImage mandelbrot = computeMandelbrot(colors);

        System.out.printf("Elapsed time: %f%n", 1e-3 * System.currentTimeMillis() - start);
        start = 1e-3 * System.currentTimeMillis();
        System.out.println("Writing to file...");
        ImageIO.write(mandelbrot, "png", new File("mandelbrot.png"));
        System.out.printf("Elapsed time: %f%n", 1e-3 * System.currentTimeMillis() - start);

        System.out.printf("%nTotal time: %f%n", 1e-3 * System.currentTimeMillis() - veryBeginning);
    }
}

As is, after running for 219 seconds, the code generates this image (screenshot as file is ~40MB):

enter image description here

I have no idea why the outside of the Mandelbrot is so random; it should have made use of my exterior distance estimate. Perhaps the inside is stealing too many of the colors....

If I remove the randomization on the points, I get this:

enter image description here

I get the feeling my exterior distance code is not working right...

\$\endgroup\$
2
  • \$\begingroup\$ exteriorDistanceEstimate() consistently returns NaN \$\endgroup\$ Jan 25, 2016 at 20:30
  • \$\begingroup\$ @MarkJeronimus I figured that out. It returns NaN almost all the time (there are some times where it doesn't though) because the double values always get too large, resulting in Infinity, so dividing makes NaN. I've been working out some of the bugs and it's getting better \$\endgroup\$
    – Justin
    Jan 25, 2016 at 21:48
4
\$\begingroup\$

Mathematica

Non-competing because the builtt-in HilbertCurve was introduced in Mathematica 11.1 in 2017.

Inspired by Joe K's answer. Generates 9 images in one program. Arranges the colors in three different orderings: the lexicographical order, the Z-order curve, the Hilbert curve; then places them in the image in (the 2-dimensional version of) these three orderings.

f[m_, n_] := {Tuples[Range[2^m] - 1, n],
   FromDigits[Partition[#, n], 2] & /@ Tuples[{0, 1}, n*m],
   First@HilbertCurve[m, n]};

Table[Image@Partition[a[[Ordering@b]]/2^6, 2^9], {a, f[6, 3]}, {b, f[9, 2]}]

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

enter image description here

\$\endgroup\$
3
\$\begingroup\$

Java

This is similar to my other answer, but

By inserting this code:

for (int iy = 0; iy < 256; iy++) {
    for (int ix = 0; ix < 256; ix++) {
        for (int y = 0; y < 16; y++) {
            for (int x = 0; x < 16; x++) {
                int rgb = img_cln.getRGB((x << 8) + ix, (y << 8) + iy);
                img.setRGB(x + (ix << 4), y + (iy << 4), rgb);
            }
        }
    }
}

Like so:

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;

/**
 *
 * @author Quincunx
 */
public class AllColorImage {

    public static void main(String[] args) {
        BufferedImage img = new BufferedImage(4096, 4096, BufferedImage.TYPE_INT_RGB);
        BufferedImage img_cln = new BufferedImage(4096, 4096, BufferedImage.TYPE_INT_RGB);

        for (int r = 0; r < 256; r++) {
            for (int g = 0; g < 256; g++) {
                for (int b = 0; b < 256; b++) {
                    img_cln.setRGB(((r & 15) << 8) | g, ((r >>> 4) << 8) | b, (((r << 8) | g) << 8) | b);
                }
            }
        }

        for (int iy = 0; iy < 256; iy++) {
            for (int ix = 0; ix < 256; ix++) {
                for (int y = 0; y < 16; y++) {
                    for (int x = 0; x < 16; x++) {
                        int rgb = img_cln.getRGB((x << 8) + ix, (y << 8) + iy);
                        img.setRGB(x + (ix << 4), y + (iy << 4), rgb);
                    }
                }
            }
        }

        try {
            ImageIO.write(img, "png", new File("Filepath"));
        } catch (IOException ex) {
            Logger.getLogger(AllColorImage.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

I can get this output:

enter image description here

I'm still trying to figure out how to remove those lines.

\$\endgroup\$
3
  • 10
    \$\begingroup\$ Have you trying turning it off and on again? \$\endgroup\$ Feb 27, 2014 at 12:36
  • 4
    \$\begingroup\$ those are caused by some LSB bits; almost all of our loop-based ones have those little annoyances and they are a royal pain in the ass to remove \$\endgroup\$
    – masterX244
    Feb 27, 2014 at 13:35
  • 5
    \$\begingroup\$ @ArlaudPierre Blowing on it might work, too. \$\endgroup\$
    – Jason C
    Feb 28, 2014 at 3:48
3
\$\begingroup\$

I know I'm a late to this discussion but I thought I would post my attempt to this thread just in case it is useful/interesting to anyone.

GLSL

vec3 xy2rgb3( in vec2 pos ) {
    vec2 rc = pos * 8.0;
    vec2 mp = fract( rc );
    rc = (rc - mp) * 0.125;
    return vec3( (rc.y * 0.125) + rc.x, mp );
}

The live editable version can be found here xy2rgb3/rgb3toxy2.

enter image description here

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

Mathematica

Flatten[Table[{r, g, b}, {r, 0, 1, 1/31}, {g, 0, 1, 1/31}, {b, 0, 1, 
 1/31}], 2]~Partition~256 // Image

enter image description here

RandomSample@
   Flatten[Table[{r, g, b}, {r, 0, 1, 1/31}, {g, 0, 1, 1/31}, {b, 0, 1, 
 1/31}], 2]~Partition~256 // Image

enter image description here

\$\endgroup\$
5
  • \$\begingroup\$ > or grid that can be screenshot and saved at 256×128 Please do this or I'll have to disqualify it \$\endgroup\$ Feb 26, 2014 at 9:13
  • \$\begingroup\$ Still not exactly right, and also I count way less colors than supposed, including stretches of equal colors delimited by what looks like color-reducing dithering. \$\endgroup\$ Feb 26, 2014 at 9:31
  • \$\begingroup\$ Better: n=31;ArrayPlot[ Partition[ Flatten@Table[ RGBColor[r/n, g/n, b/n], {r, 0, n}, {g, 0, n}, {b, 0, n}], 256], ImageSize -> {256, 128}] \$\endgroup\$
    – DavidC
    Feb 26, 2014 at 17:57
  • \$\begingroup\$ Zom-B. You may express your disagreement or dislike for a submission. But you cannot, by yourself, disqualify it. \$\endgroup\$
    – DavidC
    Feb 26, 2014 at 19:40
  • \$\begingroup\$ Shorter version: Image[Range[0,1,1/31]~Tuples~3~Partition~256] \$\endgroup\$ Mar 1, 2014 at 10:59
2
\$\begingroup\$

Processing with Xorshift

Xorshift is a type of PRNG which is very fast and somewhat reliable. A 16bit Xorshift generator will cycle through the numbers 1 to 65535, so for this task we skip any with the high bit set. What it won't do is ever produce the number 0, so we start painting at the second pixel. Each seed will produce the same result, and if you like you can try other seeds. Try it online.

enter image description here

void setup() {
    size(256,128);
    background(0);
}

int seed=1;
int xorshift() {
    seed ^= (seed << 13);
    seed ^= (seed >> 9);
    seed ^= (seed << 7);
}

void draw() {
    int i=1;
    while (i < 32768) {
        xorshift();
        while (seed > 32767) {
            xorshift();
        }
        stroke(seed<<3&255,seed>>2&255,seed>>7&255);
        point(i&255,floor(i/256));
        i++;
    }
    exit();
}

(Thank you to ace for the Processing boilerplate code.)

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Just use all the values above 32767, discard the top bit, and you don't have to skip 0. \$\endgroup\$ Oct 14, 2014 at 11:02
2
\$\begingroup\$

Pillow, Random Color Sorts

For k iterations, randomly pick (a row/col, range of pixels in that row/col, color), and sort in that range.

This produces a weave effect. (I also experimented with partitioning instead of sorting, with similar results) Observation: it's actually very simple to produce PPM output without an image library such as in pure C. Fancy numpy indexing is not necessary.

import sys
import numpy as np
from PIL import Image
import random

BIT_DEPTH = 6
ITERS = int(sys.argv[2])

IMAGE_HEIGHT = 2 ** ((3*BIT_DEPTH)//2)
IMAGE_WIDTH = 2 ** ((3*BIT_DEPTH+1)//2)
COLOR_BITS = 8 - BIT_DEPTH  # colors range(0, 256, 2**COLOR_BITS)

def check_image(a):
    assert(a.shape == (IMAGE_HEIGHT, IMAGE_WIDTH, 3))
    check = np.zeros((1 << BIT_DEPTH,) * 3, np.bool_)

    for h in range(IMAGE_HEIGHT):
        for w in range(IMAGE_WIDTH):
            r, g, b = a[h,w,:] >> COLOR_BITS
            assert not check[r,g,b]
            check[r,g,b] = True


def main():
    # fill flat array with all the colors
    a = np.empty((IMAGE_WIDTH * IMAGE_HEIGHT, 3), np.uint8)

    for i in range(IMAGE_WIDTH * IMAGE_HEIGHT):
        r = (i >> 2*BIT_DEPTH)
        g = ((i >> BIT_DEPTH) % (1 << BIT_DEPTH))
        b = (i % (1 << BIT_DEPTH))
        a[i] = np.array((r, g, b)) << COLOR_BITS

    np.random.shuffle(a)
    a = a.reshape((IMAGE_HEIGHT, IMAGE_WIDTH, 3))

    for i in range(ITERS):
        color = random.randrange(3)
        axis = random.randrange(2)

        h, h0, h1 = random.choices(range(IMAGE_HEIGHT), k=3)
        w, w0, w1 = random.choices(range(IMAGE_WIDTH), k=3)
        h0, h1 = min(h0, h1), max(h0, h1)
        w0, w1 = min(w0, w1), max(w0, w1)

        if axis:
            ind = np.argsort(a[h0:h1+1,w,color])
            a[h0:h1+1,w,:] = a[h0:h1+1,w,:][ind]
        else:
            ind = np.argsort(a[h,w0:w1+1,color])
            a[h,w0:w1+1,:] = a[h,w0:w1+1,:][ind]


    check_image(a)

    im = Image.fromarray(a)
    im.save(sys.argv[1])


main()

enter image description here

Previously I had a bug with my code that permuted color channels instead of pixels. Now the code has a check to ensure all colors are represented.

\$\endgroup\$
4
  • \$\begingroup\$ May I wonder what does the result tend to if you sometimes shuffle rows/columns as well? \$\endgroup\$ Apr 13, 2020 at 4:23
  • \$\begingroup\$ I misunderstood the prompt. Remind me to fix this code \$\endgroup\$
    – qwr
    Apr 13, 2020 at 8:54
  • \$\begingroup\$ Very unique idea. At first I wondered where your 'comparator' was in the code, but you don't use a comparator and just sort one band at random. It might be interesting if you use a comparator to sort by gray value, hue, saturation, etc. And for pro, what if your sort condition smoothly interpolates between two depending on the x or y position? \$\endgroup\$ Apr 13, 2020 at 9:53
  • \$\begingroup\$ Images using comparing pixels will probably be a separate answer of mine. \$\endgroup\$
    – qwr
    Apr 13, 2020 at 18:02
1
\$\begingroup\$

Excel VBA, 165 bytes

A declared subroutine that takes no input and outputs to a 256 x 128 cell image to the range A1:IV128. For this algorithm, red values change as a function of the column number, blue values changes as a function of row number and green values change as a function of both the column and row number, with low values falling at the top left and high values falling at the bottom right.[!

Option Compare Text
Option Explicit
Option Base 0

Public Sub c()

    Dim row As Integer, _
        col As Integer

    Let Cells.ColumnWidth = 2.15
    Let Cells.RowHeight   = 16
    For col = rgbBlack To rgbRed
        For row = rgbBlack To 127
            Let Cells(row + 1, col + 1).Interior.Color = RGB( _
                                                        Red     := 8 * (col Mod 32), _
                                                        Green   := 8 * (Int(col / 32) + 8 * Int(row / 32)), _
                                                        Blue    := 8 * (row Mod 32))
    Next row, col
End Sub

This may be golfed down to a VBE immediate window function worth 165 bytes.

Cells.ColumnWidth=2.15:Cells.RowHeight=16:For c=0To 255:For r=0To 127:Cells(r+1,c+1).Interior.Color=RGB(8*(c Mod 32),8*(Int(c/32)+8*Int(r/32)),8*(r Mod 32)):Next r,c

Output

All Colors from Excel VBA

\$\endgroup\$
0
\$\begingroup\$

C

1 2

The one above is a huge 2048x1024 image modified from the one below to conform to the rules.

I originally generated the one below by accident.

This is the code. It's so simple, but I have no idea how it's generating those patterns. The SIMPLE version was created by accident, but it was so beautiful to my eyes when I first saw it.

#ifdef SIMPLE
    #define H 768
    #define W 768
#else
    #define H 1024
    #define W 2048
#endif

#ifdef SIMPLE
void allclr(uint32_t *px) {
    for (uint32_t i = 0; i < H; ++i) {
        for (uint32_t j = 0; j < W; ++j) {
            *px++ = i * j;
        }
    }
}
#else
void allclr(uint32_t *px) {
    for (uint32_t i = 0; i < H; ++i) {
        for (uint32_t j = 0; j < W; ++j) {
            uint32_t p = i * j;
            p = (p & 0x1f) << 3 | (p & 0x3e0) << 6 | (p & 0x7c00) << 9;
            *px++ = p;
        }
    }
}
#endif

The big version is created by masking 5 bits and placing them in the upper 8-bit borders, also with a bigger size. This way the image conforms to the "use all 15-bit colors" rule. The colors will appear like that if your video memory has a layout of BGRX, from low to high, which probably is true for most x86 modern desktop environments.

If you have any idea how these patterns are appearing, any explanation would be grateful.


main.c boilerplate for SDL initialization, and the build script.

#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include "allclr.h"

static void chk(int ok) {
    if (!ok) {
        abort();
    }
}

int main(int argc, char **argv) {
    chk(!SDL_Init(SDL_INIT_VIDEO));
    SDL_Window *w = SDL_CreateWindow("allclr", SDL_WINDOWPOS_UNDEFINED,
    SDL_WINDOWPOS_UNDEFINED, W, H, 0);
    SDL_Surface *s = SDL_GetWindowSurface(w);
    chk(s);
    int ml = SDL_MUSTLOCK(s);
    if (ml) {
        chk(!SDL_LockSurface(s));
    }
    allclr(s->pixels);
    if (ml) {
        SDL_UnlockSurface(s);
    }
    chk(!SDL_UpdateWindowSurface(w));
    SDL_Event e;
    while (SDL_WaitEvent(&e)) {
        if (e.type == SDL_QUIT) {
            goto exit;
        }
    }
    abort();
exit:
    SDL_DestroyWindow(w);
    SDL_Quit();
    return 0;
}

/*
O="-O3 -march=native"
F="$O -DSIMPLE"
gcc -c $F allclr.c
gcc -or $F `pkg-config sdl2 --cflags --libs` *.o main.c
*/

BTW, if you know how to take a screenshot on Linux without the window border, please let me know. My system is Fedora with Gnome and Wayland. The tools that used to work under X11 doesn't seem to work with Wayland.

\$\endgroup\$
1
  • \$\begingroup\$ Wayland is ahead of it's time. Screenshot progs need to be updated to bypass Wayland's 'security features' preventing screen grabbing. \$\endgroup\$ Feb 20, 2022 at 17:54
1
2

Your Answer

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

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