Изображения со всеми цветами


433

Подобно изображениям на allrgb.com , создайте изображения, в которых каждый пиксель имеет уникальный цвет (цвет не используется дважды, и цвет не пропущен).

Дайте программу, которая генерирует такое изображение, а также снимок экрана или файл вывода (загрузить как PNG).

  • Создайте изображение чисто алгоритмически.
  • Изображение должно быть 256 × 128 (или сетка, которая может быть скриншотом и сохранена в 256 × 128)
  • Используйте все 15-битные цвета *
  • Не допускается внешний ввод (также не допускаются веб-запросы, URL-адреса или базы данных)
  • Не допускаются встроенные изображения (исходный код, который является изображением в порядке, например, Piet )
  • Дизеринг разрешен
  • Это не конкурс коротких кодов, хотя он может выиграть ваши голоса.
  • Если вы действительно готовы принять вызов, выполните 512 × 512, 2048 × 1024 или 4096 × 4096 (с шагом 3 бита).

Подсчет очков осуществляется путем голосования. Голосуйте за самые красивые изображения, сделанные с помощью самого элегантного кода и / или интересного алгоритма.

Двухступенчатые алгоритмы, в которых вы сначала генерируете красивое изображение, а затем подгоняете все пиксели к одному из доступных цветов, конечно, допустимы, но не принесут вам очков элегантности.

* 15-битные цвета - это 32768 цветов, которые можно получить, смешав 32 красных, 32 зеленых и 32 синего цвета, все на равных расстояниях и в одинаковых диапазонах. Пример: в 24-битных изображениях (8 бит на канал) диапазон на канал составляет 0..255 (или 0..224), поэтому разделите его на 32 одинаково разнесенных оттенка.

Чтобы быть очень ясным, массив пикселей изображения должен быть перестановкой, потому что все возможные изображения имеют одинаковые цвета, только в разных местах пикселей. Я приведу здесь тривиальную перестановку, которая совсем не красива:

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();
        }
    }
}

введите описание изображения здесь

победитель

Поскольку 7 дней закончились, я объявляю победителя

Однако ни в коем случае не думайте, что все кончено. Я и все читатели всегда приветствуем более классные дизайны. Не прекращай творить.

Победитель: fejesjoco с 231 голосом


8
Когда вы говорите «Дизеринг разрешен», что вы имеете в виду? Является ли это исключением из правила «каждый пиксель - это уникальный цвет»? Если нет, что вы разрешаете, что было запрещено в противном случае?
Питер Тейлор

1
Это означает, что вы можете размещать цвета в шаблоне, поэтому при просмотре глазами они смешиваются в другой цвет. Например, см. Изображение «ясно, все RGB» на странице allRGB и многие другие.
Марк Жеронимус

8
Я на самом деле нахожу ваш тривиальный пример перестановки весьма приятным для глаз.
Джейсон C

2
@ Zom-B Человек, я чертовски люблю этот пост. Спасибо!
Джейсон C

7
Красивые результаты / ответы!
EthanB

Ответы:


534

C #

Я помещаю случайный пиксель посередине, а затем начинаю размещать случайные пиксели в окрестности, которая больше всего напоминает их. Поддерживаются два режима: при минимальном выборе одновременно рассматривается только один соседний пиксель; при среднем выборе все (1..8) усредняются. Минимальный выбор несколько шумный, средний выбор, конечно, более размытый, но на самом деле оба выглядят как картины. После некоторого редактирования, вот текущая, несколько оптимизированная версия (она даже использует параллельную обработку!):

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 пикселей, начиная с середины, минимальный выбор:

256x128 пикселей, начиная с верхнего левого угла, минимальный выбор:

256x128 пикселей, начиная с середины, средний выбор:

Вот два 10-кадровых анимационных изображения, которые показывают, как работает минимальный и средний выбор (спасибо формату gif за возможность отображать его только с 256 цветами):

Режим выделения минимума растет с небольшим волновым фронтом, похожим на каплю, заполняющую все пиксели по мере их появления. Однако в усредненном режиме, когда две разноцветные ветви начинают расти рядом друг с другом, будет небольшой черный разрыв, потому что ничто не будет достаточно близко к двум разным цветам. Из-за этих пробелов волновой фронт будет на порядок больше, поэтому алгоритм будет намного медленнее. Но это приятно, потому что выглядит как растущий коралл. Если бы я отбросил средний режим, его можно было бы сделать немного быстрее, потому что каждый новый цвет сравнивался с каждым существующим пикселем примерно в 2-3 раза. Я не вижу других способов оптимизировать его, я думаю, что он достаточно хорош, как есть.

И большая привлекательность, это рендеринг 512x512 пикселей, средний старт, минимальный выбор:

Я просто не могу перестать играть с этим! В приведенном выше коде цвета сортируются случайным образом. Если мы вообще не сортируем или сортируем по hue ( (c1, c2) => c1.GetHue().CompareTo(c2.GetHue())), мы получаем их соответственно (и средний старт, и минимальный выбор):

Другая комбинация, где коралловая форма сохраняется до конца: цветовой порядок упорядочен со средним выбором, с 30-кадровым анимационным изображением:

ОБНОВЛЕНИЕ: ЭТО ГОТОВО !!!

Вы хотели привет, я хотел привет, вы были нетерпеливы, я едва спал. Теперь я рад сообщить, что, наконец, готово, качество продукции. И я выпускаю его с большим взрывом, потрясающее видео на YouTube 1080p! Нажмите здесь для видео , давайте сделаем его вирусным, чтобы продвинуть стиль гика. Я также размещаю материал в своем блоге по адресу http://joco.name/ , там будет технический пост обо всех интересных деталях, оптимизации, том, как я сделал видео и т. Д. И, наконец, я делюсь источником код под GPL. Он стал огромным, поэтому подходящий хостинг - лучшее место для этого, я больше не буду редактировать вышеупомянутую часть моего ответа. Обязательно компилируем в режиме релиза! Программа хорошо масштабируется на многие ядра процессора. Для рендеринга 4Kx4K требуется около 2-3 ГБ ОЗУ.

Теперь я могу рендерить огромные изображения за 5-10 часов. У меня уже есть некоторые 4Kx4K рендеры, я опубликую их позже. Программа сильно продвинулась, было проведено множество оптимизаций. Я также сделал его удобным для пользователя, так что любой может легко использовать его, у него есть хорошая командная строка. Программа также является детерминистически случайной, что означает, что вы можете использовать случайное начальное число, и оно будет каждый раз генерировать одно и то же изображение.

Вот некоторые большие рендеры.

Мой любимый 512:


(источник: joco.name )

2048, которые появляются в моем видео :


(источник: joco.name )


(источник: joco.name )


(источник: joco.name )


(источник: joco.name )

Первые 4096 рендеров (TODO: они загружаются, и мой сайт не может обрабатывать большой трафик, поэтому они временно перемещены):


(источник: joco.name )


(источник: joco.name )


(источник: joco.name )


(источник: joco.name )


25
Теперь это круто!
Jaa-c

5
Очень приятно :-D Теперь сделайте несколько больших!
брезгливое оссифраж

20
Ты настоящий художник! :)
AL

10
Сколько стоит печать?
Примо

16
Я работаю над огромными рендерами и видео 1080p. Я собираюсь занять часы или дни. Я надеюсь, что кто-то сможет создать принт из большого рендера. Или даже футболку: код с одной стороны, изображение с другой. Кто-нибудь может это устроить?
fejesjoco

248

обработка

Обновить! 4096x4096 изображений!

Я объединил свой второй пост в этот, объединив две программы вместе.

С полной коллекцией выбранных изображений можно ознакомиться здесь, на Dropbox . (Примечание: DropBox не может создавать предварительные просмотры для изображений 4096x4096; просто нажмите на них, затем нажмите «Скачать»).

Если вы только посмотрите на один взгляд на этот (мозаичный)! Здесь он уменьшен (и еще много ниже), оригинальный 2048x1024:

введите описание изображения здесь

Эта программа работает, проходя пути из случайно выбранных точек в цветном кубе, затем рисуя их в случайно выбранные пути на изображении. Есть много возможностей. Настраиваемые параметры:

  • Максимальная длина пути цветового куба.
  • Максимальный шаг для прохождения цветового куба (большие значения вызывают большую дисперсию, но сводят к минимуму количество маленьких путей к концу, когда все становится напряженно).
  • Черепица изображения.
  • В настоящее время существует два режима прохождения изображения:
    • Режим 1 (режим этого исходного сообщения): находит блок неиспользуемых пикселей на изображении и выполняет рендеринг в этот блок. Блоки могут быть расположены случайным образом или упорядочены слева направо.
    • Режим 2 (режим моего второго поста, который я слил в этот): выбирает случайную начальную точку на изображении и идет по пути через неиспользуемые пиксели; может ходить вокруг используемых пикселей. Опции для этого режима:
      • Набор направлений для ходьбы (ортогональный, диагональный или оба).
      • Следует ли менять направление (в настоящее время по часовой стрелке, но код является гибким) после каждого шага или менять направление только при обнаружении занятого пикселя.
      • Возможность смены порядка изменения направления (вместо по часовой стрелке).

Работает для всех размеров до 4096х4096.

Полный эскиз обработки можно найти здесь: Tracer.zip

Я вставил все файлы в один и тот же блок кода ниже, чтобы сэкономить место (даже все в одном файле, это все еще допустимый эскиз). Если вы хотите использовать одну из предустановок, измените индекс в gPresetназначении. Если вы запустите это в обработке, вы можете нажать rво время его работы для создания нового изображения.

  • Обновление 1: оптимизированный код для отслеживания первого неиспользуемого цвета / пикселя, а не поиска по известным использованным пикселям; уменьшено время генерации 2048x1024 с 10-30 минут до 15 секунд и 4096x4096 с 1-3 часов до 1 минуты. Drop box source и source ниже обновлены.
  • Обновление 2: исправлена ​​ошибка, препятствовавшая генерации изображений 4096x4096.
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;
  }

}

Вот полный набор изображений размером 256x128, которые мне нравятся:

Режим 1:

Мой любимый из оригинального набора (max_path_length = 512, path_step = 2, случайный, отображается 2x, ссылка 256x128 ):

введите описание изображения здесь

Другие (слева два упорядоченных, два справа справа, длина двух верхних путей ограничена, два нижних не ограничены):

ordlimit randlimit ordnolimit randnolimit

Этот может быть выложен плиткой:

randtile

Режим 2:

бриллианты цветы boxfade diagover bigdiamonds boxes2 осколки

Эти могут быть выложены плиткой:

bigtile diamondtile подарочная упаковка

512x512 выборов:

Бесшовное бриллианты, мой любимый из режима 2; в этом вы можете увидеть, как пути проходят вокруг существующих объектов:

введите описание изображения здесь

Большой шаг пути и максимальная длина пути:

введите описание изображения здесь

Случайный режим 1, мозаичный:

введите описание изображения здесь

Больше выборов:

введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь

Все визуализации 512x512 можно найти в папке dropbox (* _64.png).

2048x1024 и 4096x4096:

Они слишком велики, чтобы встраивать их, и все найденные мной хосты изображений сбрасывают их до 1600x1200. В настоящее время я отрисовываю набор изображений размером 4096х4096, так что скоро будет доступно больше. Вместо того, чтобы включать все ссылки здесь, просто проверьте их в папке dropbox (* _128.png и * _256.png, обратите внимание: 4096x4096 слишком велики для предпросмотра dropbox, просто нажмите «скачать»). Вот некоторые из моих любимых, хотя:

Большие алмазы размером 2048x1024 (те, на которые я ссылался в начале этого поста)

2048x1024 бриллиантов (я люблю этот!), Уменьшено:

введите описание изображения здесь

4096x4096 больших мозаичных бриллиантов (наконец-то! Нажмите ссылку «скачать» в ссылке Dropbox; они слишком велики для их предварительного просмотра) в уменьшенном размере:

4096x4096 больших алмазов

4096x4096 случайный режим 1 : введите описание изображения здесь

4096x4096 еще один крутой

Обновление: набор предустановленных изображений 2048x1024 завершен и находится в раскрывающемся списке. Набор 4096x4096 должен быть сделан в течение часа.

Есть много хороших, мне очень трудно выбрать, какие из них опубликовать, поэтому, пожалуйста, проверьте ссылку на папку!


6
Это напоминает мне о близком расстоянии некоторых минералов.
Morwenn

3
Не участвовал в конкурсе, но я подумал, что это круто ; Я применил большое размытие по Гауссу и улучшение автоконтрастности к одному из фото в случайном режиме 1 в фотошопе, и это сделало довольно приятный фоновый рисунок рабочего стола.
Джейсон C

2
Вау, это классные картинки!
sevenseacat

2
Напоминает мне текстуры Густава Климта.
Ким

2
Знаете ли вы, вы можете хотлинк изображения в Dropbox? Просто скопируйте URL скачать, удалить dl=1и на token_hash=<something>части и сделать ссылку на изображение , как это: [![Alt text of my small preview image](https://i.stack.imgur.com/smallpreview.png)](https://dl.dropbox.com/linktoyourfullsiz‌​eimage.png). Еще один совет: вы можете сжать ваши изображения (я получаю хорошие результаты с TruePNG ( скачать )). Мне удалось сохранить 28,1% от размера файла на этом изображении .
user2428118

219

Python W / PIL

Это основано на ньютоновском фрактале , особенно при z → z 5 - 1 . Поскольку имеется пять корней и, следовательно, пять точек конвергенции, доступное цветовое пространство разделено на пять областей на основе оттенка. Отдельные точки сортируются сначала по количеству итераций, необходимых для достижения их точки сходимости, а затем по расстоянию до этой точки, причем более ранним значениям присваивается более яркий цвет.

Обновление: 4096x4096 больших рендеров , размещенных на allrgb.com .

Оригинал (33,7 МБ)

Крупный план самого центра (фактический размер):

Другая точка зрения, использующая эти значения:

xstart = 0
ystart = 0

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

Оригинал (32,2 МБ)

И еще один, использующий эти:

xstart = 0.5
ystart = 0.5

xd = 0.001 / dim[0]
yd = 0.001 / dim[1]

Оригинал (27,2 МБ)


Анимация

По запросу я собрал анимацию увеличения.

Фокус : ( 0.50051 , -0.50051 )
Коэффициент увеличения: 2 1/5

Фокусная точка - немного странное значение, потому что я не хотел увеличивать черную точку. Коэффициент масштабирования выбран таким, чтобы он удваивался каждые 5 кадров.

Тизер 32х32:

256x256 версию можно увидеть здесь:
http://www.pictureshack.org/images/66172_frac.gif (5,4 МБ)

Могут быть точки, которые математически увеличивают масштаб «на себя», что обеспечивает бесконечную анимацию. Если я смогу их идентифицировать, я добавлю их сюда.


Источник

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")

6
Наконец-то фрактал :) Люблю тех. Кроме того, этот зеленый при 144 градусах - мой любимый цвет (в отличие от чистого зеленого при 120 градусах, который просто скучен).
Марк Жеронимус

2
Я не знаю, на самом деле мне больше нравятся версии AllRGB; необходимость использовать все пространство яркости красиво подчеркивает градиенты.
Ильмари Каронен

2
+1 Наконец-то хорошие фракталы! Последний мой любимый. Вы должны сделать видео с увеличением! (@Quincunx: видел и тебя; у меня был мой голос с 1-го дня!)
Джейсон С

1
@JasonC Я добавил анимацию;)
прим

2
@ primo Я знаю, что опаздываю, но я просто хотел сказать, что эти изображения впечатляют.
Эшвин Гупта

130

Я получил эту идею из алгоритма пользователя fejesjoco и хотел немного поиграть, поэтому я начал писать свой собственный алгоритм с нуля.

Я пишу это, потому что чувствую, что если я смогу сделать что-то лучше, чем лучшее из вас, ребята, я не думаю, что этот вызов еще закончен. Для сравнения, на всем RGB есть несколько потрясающих дизайнов, которые, я считаю, выходят далеко за рамки достигнутого здесь уровня, и я не представляю, как они это сделали.

*) все равно будет решаться голосованием

Этот алгоритм:

  1. Начните с (нескольких) семян, с цветами, максимально приближенными к черному.
  2. Держите список всех пикселей, которые не посещены и 8 подключены к посещенной точке.
  3. Выберите случайную ** точку из этого списка
  4. Вычислите средний цвет всех вычисленных пикселей [Редактировать ... в квадрате 9x9, используя ядро ​​Гаусса] 8, подключенный к нему (это причина, почему он выглядит так гладко) Если ничего не найдено, возьмите черный.
  5. в кубе 3x3x3 вокруг этого цвета найдите неиспользуемый цвет.
    • Когда найдены несколько цветов, возьмите самый темный.
    • Когда найдены несколько одинаково темных цветов, возьмите случайный из них.
    • Если ничего не найдено, обновите диапазон поиска до 5x5x5, 7x7x7 и т. Д. Повторите с 5.
  6. Построить пиксель, обновить список и повторить с 3

Я также экспериментировал с различными вероятностями выбора точек-кандидатов на основе подсчета количества посещенных соседей выбранного пикселя, но это только замедляло алгоритм, не делая его более красивым. Текущий алгоритм не использует вероятности и выбирает случайную точку из списка. Это приводит к тому, что точки с множеством соседей быстро заполняются, превращаясь в растущий сплошной шар с размытым краем. Это также предотвращает недоступность соседних цветов, если щели должны были быть заполнены позже в процессе.

Изображение тороидальное.

Ява

Скачать: com.digitalmodularбиблиотека

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
  • оригинал 1 семя
  • 1 секунда

введите описание изображения здесь

  • 2048 × 1024
  • слегка плиточный на рабочий стол 1920 × 1080
  • 30 секунд
  • негатив в фотошопе

введите описание изображения здесь

  • 2048 × 1024
  • 8 семян
  • 27 секунд

введите описание изображения здесь

  • 512 × 512
  • 40 случайных семян
  • 6 секунд

введите описание изображения здесь

  • 4096 × 4096
  • 1 семя
  • Полосы становятся значительно острее (так как они выглядят так, как будто они могут нарезать рыбу в сашими)
  • Выглядело, как будто все закончилось за 20 минут, но ... по какой-то причине не удалось завершить, так что теперь я запускаю 7 экземпляров параллельно в одночасье.

[Смотри ниже]

[Править]
** Я обнаружил, что мой метод выбора пикселей не был абсолютно случайным. Я думал, что случайная перестановка пространства поиска будет случайной и быстрее реальной случайности (потому что точка не будет выбрана дважды случайно. Однако каким-то образом, заменяя ее реальной случайной, я постоянно получаю больше шумовых пятен на своем изображении.

[код версии 2 удален, потому что я превысил ограничение в 30 000 символов]

введите описание изображения здесь

  • Увеличен начальный куб поиска до 5x5x5

введите описание изображения здесь

  • Еще больше, 9x9x9

введите описание изображения здесь

  • Авария 1. Отключена перестановка, поэтому пространство поиска всегда линейно.

введите описание изображения здесь

  • Авария 2. Опробовал новую технику поиска с использованием очереди fifo. Все еще нужно проанализировать это, но я подумал, что стоит поделиться.

введите описание изображения здесь

  • Всегда выбирая в пределах X неиспользуемые пиксели от центра
  • X колеблется от 0 до 8192 с шагом 256

Изображение не может быть загружено: «Ой! Произошло что-то плохое! Это не ты, это мы. Это наша вина». Изображение слишком большое для imgur. Попытка в другом месте ...

введите описание изображения здесь

Экспериментируя с пакетом планировщика, я нашел в digitalmodularбиблиотеке, чтобы определить порядок, в котором обрабатываются пиксели (вместо диффузии).

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;
    }
}
  • Угловая (8)

введите описание изображения здесь

  • Угловая (64)

введите описание изображения здесь

  • ЭЛТ

введите описание изображения здесь

  • трястись

введите описание изображения здесь

  • Цветок (5, X), где X колеблется от 0,5 до 20 с шагом X = X × 1,2

введите описание изображения здесь

  • модификация

введите описание изображения здесь

  • Пифагор

введите описание изображения здесь

  • радиальный

введите описание изображения здесь

  • случайный

введите описание изображения здесь

  • Scanline

введите описание изображения здесь

  • Спираль (X), где X колеблется от 0,1 до 200 с шагом X = X × 1,2
  • Вы можете видеть, что он колеблется между радиальным и угловым (5)

введите описание изображения здесь

  • Трещина

введите описание изображения здесь

  • SquareSpiral

введите описание изображения здесь

  • XOR

введите описание изображения здесь

Новая глазная еда

  • Эффект выбора цвета max(r, g, b)

введите описание изображения здесь

  • Эффект выбора цвета min(r, g, b)
  • Обратите внимание, что этот имеет те же функции / детали, что и выше, только с разными цветами! (то же случайное семя)

введите описание изображения здесь

  • Эффект выбора цвета max(r, min(g, b))

введите описание изображения здесь

  • Эффект выбора цвета по значению серого 299*r + 436*g + 114*b

введите описание изображения здесь

  • Эффект выбора цвета 1*r + 10*g + 100*b

введите описание изображения здесь

  • Эффект выбора цвета 100*r + 10*g + 1*b

введите описание изображения здесь

  • Счастливые несчастные случаи при 299*r + 436*g + 114*bпереполнении в 32-разрядное целое число

введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь

  • Вариант 3, со значением серого и радиальным планировщиком

введите описание изображения здесь

  • Я забыл, как я создал это

введите описание изображения здесь

  • В планировщике CRT также была счастливая ошибка переполнения целых чисел (обновлен ZIP), из-за чего он начинался наполовину, с 512 × 512 изображениями, а не в центре. Вот как это должно выглядеть:

введите описание изображения здесь введите описание изображения здесь

  • InverseSpiralScheduler(64) (Новый)

введите описание изображения здесь

  • Еще один XOR

введите описание изображения здесь

  • Первый успешный рендер 4096 после исправления. Я думаю, что это была версия 3 SpiralScheduler(1)или что-то

введите описание изображения здесь (50Мб !!)

  • Версия 1 4096, но я случайно оставил критерии цвета на max()

введите описание изображения здесь (50Мб !!)

  • 4096, теперь с min()
  • Обратите внимание, что этот имеет те же функции / детали, что и выше, только с разными цветами! (то же случайное семя)
  • Время: забыл записать, но отметка времени файла через 3 минуты после изображения до

введите описание изображения здесь (50Мб !!)


Здорово. Ваше окончательное изображение похоже на вторую идею, которую я обсуждаю, хотя у меня есть ощущение, что моя не будет выглядеть так же хорошо. Кстати, есть такой же крутой на allrgb.com/diffusive .
Джейсон С

Он был задуман как просто тизер, но я отредактировал его, боясь быть помеченным, что, по-видимому, произошло :)
Mark Jeronimus

2
Даже несчастные случаи выглядят хорошо :). Цветной кубик кажется очень хорошей идеей, и скорость рендеринга по сравнению с моей удивительна. Некоторые проекты на allrgb имеют хорошее описание, например, allrgb.com/dla. Хотелось бы, чтобы у меня было больше времени для экспериментов, было бы так много возможностей ...
fejesjoco

Я почти забыл, я только что загрузил некоторые из моих больших рендеров. Я думаю, что один из них, радужный дым / пролитые чернила, лучше всего на allrgb :). Я согласен, остальные не такие потрясающие, поэтому я сделал видео, чтобы сделать из них что-то еще :).
fejesjoco

Добавлен исходный код и ссылка на библиотеку Digisoft, так что вы можете скомпилировать мой код
Mark Jeronimus

72

C ++ с Qt

Я вижу вашу версию:

введите описание изображения здесь

используя нормальное распределение для цветов:

введите описание изображения здесь введите описание изображения здесь

или сначала отсортированы по красному / оттенку (с меньшим отклонением):

введите описание изображения здесь введите описание изображения здесь

или некоторые другие дистрибутивы:

введите описание изображения здесь введите описание изображения здесь

Распределение Коши (hsl / красный):

введите описание изображения здесь введите описание изображения здесь

отсортировано по легкости (hsl):

введите описание изображения здесь

обновлен исходный код - выдает 6-е изображение:

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;
}

Красиво сделано. Тем не менее, не может ли image.pixel(x, y) == 0выйти из строя и перезаписать первый размещенный пиксель?
Марк Жеронимус

@ Zom-B: может, но тогда последний будет черным, так что это в рамках правил ..
Jaa-c

Нет проблем с правилами, хотя. Я просто подумал, что вы могли пропустить это. Тогда можно считать от 1. Я люблю твои другие!
Марк Джеронимус

@ Zom-B: спасибо, я мог бы добавить еще немного, мне это нравится: P
Jaa-c

Тот, что с двумя кругами и тот, что под ним вместе, выглядит как обезьянье лицо.
Джейсон C

64

В 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();
        }
    }

}

и входное изображение:

лемур

Я генерирую что-то вроде этого:

acidLemur

Несжатая версия здесь: https://www.mediafire.com/?7g3fetvaqhoqgh8

Мой компьютер занимает примерно 30 минут, чтобы создать изображение 4096 ^ 2, что является огромным улучшением по сравнению с 32 днями, которые потребовались бы для моей первой реализации.


1
уч; 32 дня не звучали смешно ..... средний алгоритм в ответе fejesjocos на 4k, прежде чем оптимизировать, занял бы, вероятно, несколько месяцев
masterX244

5
Я люблю его панк брови!
Уровень Река St

45

Java с BubbleSort

(обычно Bubblesort не очень-то нравился, но для этого испытания он, наконец, нашел применение :) сгенерировал строку со всеми элементами на расстоянии 4096 шагов, затем перетасовал ее; сортировка прошла, и каждому подобному добавили 1 к их значению при сортировке, так что в результате вы получили отсортированные значения и все цвета

Обновлен исходный код, чтобы убрать эти большие полосы
(нужна побитовая магия: 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;
    }
}

Результат:

Старая версия

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;
    }
}

предварительный просмотр вывода


На странице allRGB уже есть версия QuickSort.
Марк Жеронимус

1
@ Zom-B Быстрая сортировка отличается от алгоритма Bubblesort
masterX244

43

С

Создает вихрь, по причинам, которые я не понимаю, с четными и нечетными кадрами, содержащими совершенно разные вихри.

Это предварительный просмотр первых 50 нечетных кадров:

предварительный просмотр вихря

Образец изображения, преобразованный из PPM в демонстрационный полный цветовой охват:

образец изображения

Позже, когда все смешается с серым, вы все еще можете видеть его вращение: более длинная последовательность .

Код следующим образом. Чтобы запустить, включите номер кадра, например:

./vortex 35 > 35.ppm

Я использовал это, чтобы получить анимированный GIF:

convert -delay 10 `ls * .ppm | сортировать -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;
}

53
Вы знаете, что это С, когда все происходит по «причинам, которые я не понимаю».
Нить

2
Да, обычно я знаю, чего ожидать, но здесь я просто поиграл, чтобы посмотреть, какие паттерны я смог получить, и появилась эта бесконечная последовательность порядка в хаосе.

8
Это завихряется, потому что ваша функция сравнения не следует неравенству треугольника. Например, r> b, b> g, g> r. Я даже не могу перенести его на Java, потому что это mergesort зависит от этого самого свойства, поэтому я получаю исключение "Метод сравнения нарушает его общий контракт!"
Марк Жеронимус

2
Я постараюсь, p->b * 6 - q->g;но если это разрушит вихрь, я не исправлю это!

4
+1 по причинам, которые я не понимаю.
Джейсон C

40

Ява

Вариации выбора цвета в 512x512. Это не элегантный код , но мне нравятся красивые картинки:

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";
    }
}

Как написано, это выводит:

без перемешивания

Если вы запустите его shuffle_block = true, он перетасует цвета в каждом блоке 64x64:

блок тасовать

Иначе, если вы запустите его shuffle_radius > 0, он перетасовывает каждый пиксель со случайным пикселем shuffle_radiusв х / у. После игры с различными размерами мне нравится радиус 32 пикселя, так как он размывает линии, не слишком перемещая объекты:

введите описание изображения здесь


3
ооо эти картинки самые красивые
sevenseacat

Это действительно здорово Mat
Мэтью

37

обработка

Я только начал работать с C (я программировал на других языках), но мне было трудно следить за графикой в ​​Visual C, поэтому я загрузил эту программу обработки, используемую @ace.

Вот мой код и мой алгоритм.

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);  
  } 
}

Алгоритм

Начните с квадратов 4x4 всех возможных комбинаций из 32 значений зеленого и синего, в x, y. формат, создавая квадрат 128x128. Каждый квадрат 4x4 имеет 16 пикселей, поэтому создайте зеркальное отображение рядом с ним, чтобы получить 32 пикселя для каждой возможной комбинации зеленого и синего цветов на каждое изображение ниже.

(как ни странно, полный зеленый цвет выглядит ярче, чем полный голубой. Это должно быть оптическим обманом. Уточнено в комментариях)

В левом квадрате добавьте красные значения 0-15. Для правого квадрата XOR эти значения с 16, чтобы сделать значения 16-31.

введите описание изображения здесь

Выход 256х128

Это дает вывод на верхнем изображении ниже.

Однако каждый пиксель отличается от своего зеркального отображения только самым значимым битом красного значения. Итак, я могу применить условие с переменнойc , чтобы обратить XOR, что имеет тот же эффект, что и обмен этими двумя пикселями.

Пример этого приведен на нижнем изображении ниже (если мы раскомментируем строку кода, которая в данный момент закомментирована).

введите описание изображения здесь

512 x 512 - Дань Мэрилин Энди Уорхола

Вдохновленный ответом Quincunx на этот вопрос со «злой усмешкой» в красных кружках от руки, вот моя версия знаменитой картины. Оригинал на самом деле имел 25 цветных Мэрилин и 25 черно-белых Мэрилин и был данью Уорхола Мэрилин после ее безвременной смерти. Смотрите http://en.wikipedia.org/wiki/Maryn_Diptych

Я переключился на другие функции после того, как обнаружил, что Processing отображает те, которые я использовал в 256x128, как полупрозрачные. Новые непрозрачны.

И хотя изображение не совсем алгоритмическое, оно мне больше нравится.

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");

}

введите описание изображения здесь

512x512 Сумерки над озером с горами на расстоянии

Здесь полностью алгоритмическая картина. Я поиграл с изменением цвета, который я модулирую с условием, но я просто возвращаюсь к выводу, что красный работает лучше всего. Как и в случае с изображением Мэрилина, сначала я рисую горы, а затем выбираю яркость на этом изображении, чтобы перезаписать положительное изображение RGB и копировать в отрицательную половину. Небольшое отличие состоит в том, что дно многих гор (поскольку все они нарисованы одинакового размера) простирается ниже области чтения, поэтому эта область просто обрезается во время процесса чтения (что, следовательно, создает желаемое впечатление от гор разных размеров. )

В этом я использую ячейку 8x4 32 красных для положительного, а остальные 32 красных для отрицательного.

Обратите внимание на команду expicit frameRate (1) в конце моего кода. Я обнаружил, что без этой команды, обработка будет использовать 100% одного ядра моего процессора, даже если он закончил рисование. Насколько я могу судить, нет функции Sleep, все, что вы можете сделать, это уменьшить частоту опроса.

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);
}

введите описание изображения здесь


Потому что это не полный голубой на всех. Это (0,217,217). Все 32 комбинации присутствуют, но не растянуты [0,255]. Изменить: вы используете шаги 7, но я не могу найти это в коде. Должно быть вещь Обработка.
Марк Джеронимус

@steveverrill При обработке вы можете save("filename.png")сохранить текущий кадр буфера в изображение. Другие форматы изображений также поддерживаются. Это избавит вас от необходимости делать скриншоты. Изображение сохраняется в папке эскиза.
Джейсон C

@ Jasonc спасибо за совет, я был уверен, что должен быть способ, но я не думаю, что я буду редактировать их. Я оставил рамку вокруг изображений частично, чтобы разделить их (2 файла для таких маленьких изображений были излишними.) Я хочу сделать некоторые изображения в 512x512 (и есть одно, в частности, у меня есть идея), поэтому я буду загружать их так, чтобы Вы предлагаете.
Уровень Ривер Стрит

1
@steveverrill Ха-ха, Warhols - приятное прикосновение.
Джейсон C

@ Zom-B Обработка, кажется, делает много вещей, которые (досадно) не упоминаются в ее документации: не использовать полные 256 логических значений цветовых каналов в своем физическом выводе, смешивая цвета, когда вы не хотите, используя полное ядро мой процессор даже после того, как он закончил рисование. Тем не менее, это просто, и вы можете обойти эти проблемы, как только узнаете, что они есть (кроме первого, я еще не решил это ...)
Level River St

35

Я только что расположил все 16-битные цвета (5r, 6g, 5b) на кривой Гильберта в JavaScript.

цвета кривой Гильберта

Предыдущее (не кривая Гильберта) изображение:

кривая Гильберта

JSfiddle: jsfiddle.net/LCsLQ/3

JavaScript

// ported code from http://en.wikipedia.org/wiki/Hilbert_curve
function xy2d (n, p) {
    p = {x: p.x, y: p.y};
    var r = {x: 0, y: 0},
        s,
        d=0;
    for (s=(n/2)|0; s>0; s=(s/2)|0) {
        r.x = (p.x & s) > 0 ? 1 : 0;
        r.y = (p.y & s) > 0 ? 1 : 0;
        d += s * s * ((3 * r.x) ^ r.y);
        rot(s, p, r);
    }
    return d;
}

//convert d to (x,y)
function d2xy(n, d) {
    var r = {x: 0, y: 0},
        p = {x: 0, y: 0},
        s,
        t=d;
    for (s=1; s<n; s*=2) {
        r.x = 1 & (t/2);
        r.y = 1 & (t ^ rx);
        rot(s, p, r);
        p.x += s * r.x;
        p.y += s * r.y;
        t /= 4;
    }
    return p;
}

//rotate/flip a quadrant appropriately
function rot(n, p, r) {
    if (r.y === 0) {
        if (r.x === 1) {
            p.x = n-1 - p.x;
            p.y = n-1 - p.y;
        }

        //Swap x and y
        var t  = p.x;
        p.x = p.y;
        p.y = t;
    }
}
function v2rgb(v) {
    return ((v & 0xf800) << 8) | ((v & 0x7e0) << 5) | ((v & 0x1f) << 3); 
}
function putData(arr, size, coord, v) {
    var pos = (coord.x + size * coord.y) * 4,
        rgb = v2rgb(v);

    arr[pos] = (rgb & 0xff0000) >> 16;
    arr[pos + 1] = (rgb & 0xff00) >> 8;
    arr[pos + 2] = rgb & 0xff;
    arr[pos + 3] = 0xff;
}
var size = 256,
    context = a.getContext('2d'),
    data = context.getImageData(0, 0, size, size);

for (var i = 0; i < size; i++) {
    for (var j = 0; j < size; j++) {
        var p = {x: j, y: i};
        putData(data.data, size, p, xy2d(size, p));
    }
}
context.putImageData(data, 0, 0);

Редактировать : Оказывается, в моей функции была ошибка для вычисления кривой Гильберта, и она была неправильной; а именно, r.x = (p.x & s) > 0; r.y = (p.y & s) > 0;изменен наr.x = (p.x & s) > 0 ? 1 : 0; r.y = (p.y & s) > 0 ? 1 : 0;

Редактировать 2: Еще один фрактал:

Серпинского

http://jsfiddle.net/jej2d/5/


Приятно! Добро пожаловать в PPCG.
Джонатан Ван Матре

Как это выглядит, когда прогулка по цветному кубу также является трехмерной кривой Гильберта? Редактировать нм. кто-то сделал именно это.
Марк Жеронимус

35

C #: итеративная локальная оптимизация сходства

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace AllColors
{
    class Program
    {
        static Random _random = new Random();

        const int ImageWidth = 256;
        const int ImageHeight = 128;
        const int PixelCount = ImageWidth * ImageHeight;
        const int ValuesPerChannel = 32;
        const int ChannelValueDelta = 256 / ValuesPerChannel;

        static readonly int[,] Kernel;
        static readonly int KernelWidth;
        static readonly int KernelHeight;

        static Program()
        {
            // Version 1
            Kernel = new int[,] { { 0, 1, 0, },
                                  { 1, 0, 1, },
                                  { 0, 1, 0, } };
            // Version 2
            //Kernel = new int[,] { { 0, 0, 1, 0, 0 },
            //                      { 0, 2, 3, 2, 0 },
            //                      { 1, 3, 0, 3, 1 },
            //                      { 0, 2, 3, 2, 0 },
            //                      { 0, 0, 1, 0, 0 } };
            // Version 3
            //Kernel = new int[,] { { 3, 0, 0, 0, 3 },
            //                      { 0, 1, 0, 1, 0 },
            //                      { 0, 0, 0, 0, 0 },
            //                      { 0, 1, 0, 1, 0 },
            //                      { 3, 0, 0, 0, 3 } };
            // Version 4
            //Kernel = new int[,] { { -9, -9, -9, -9, -9 },
            //                      {  1,  2,  3,  2,  1 },
            //                      {  2,  3,  0,  3,  2 },
            //                      {  1,  2,  3,  2,  1 },
            //                      {  0,  0,  0,  0,  0 } };
            // Version 5
            //Kernel = new int[,] { { 0, 0, 1, 0, 0, 0, 0 },
            //                      { 0, 1, 2, 1, 0, 0, 0 },
            //                      { 1, 2, 3, 0, 1, 0, 0 },
            //                      { 0, 1, 2, 0, 0, 0, 0 },
            //                      { 0, 0, 1, 0, 0, 0, 0 } };
            KernelWidth = Kernel.GetLength(1);
            KernelHeight = Kernel.GetLength(0);

            if (KernelWidth % 2 == 0 || KernelHeight % 2 == 0)
            {
                throw new InvalidOperationException("Invalid kernel size");
            }
        }

        private static Color[] CreateAllColors()
        {
            int i = 0;
            Color[] colors = new Color[PixelCount];
            for (int r = 0; r < ValuesPerChannel; r++)
            {
                for (int g = 0; g < ValuesPerChannel; g++)
                {
                    for (int b = 0; b < ValuesPerChannel; b++)
                    {
                        colors[i] = Color.FromArgb(255, r * ChannelValueDelta, g * ChannelValueDelta, b * ChannelValueDelta);
                        i++;
                    }
                }
            }
            return colors;
        }

        private static void Shuffle(Color[] colors)
        {
            // Knuth-Fisher-Yates shuffle
            for (int i = colors.Length - 1; i > 0; i--)
            {
                int n = _random.Next(i + 1);
                Swap(colors, i, n);
            }
        }

        private static void Swap(Color[] colors, int index1, int index2)
        {
            var temp = colors[index1];
            colors[index1] = colors[index2];
            colors[index2] = temp;
        }

        private static Bitmap ToBitmap(Color[] pixels)
        {
            Bitmap bitmap = new Bitmap(ImageWidth, ImageHeight);
            int x = 0;
            int y = 0;
            for (int i = 0; i < PixelCount; i++)
            {
                bitmap.SetPixel(x, y, pixels[i]);
                x++;
                if (x == ImageWidth)
                {
                    x = 0;
                    y++;
                }
            }
            return bitmap;
        }

        private static int GetNeighborDelta(Color[] pixels, int index1, int index2)
        {
            return GetNeighborDelta(pixels, index1) + GetNeighborDelta(pixels, index2);
        }

        private static int GetNeighborDelta(Color[] pixels, int index)
        {
            Color center = pixels[index];
            int sum = 0;
            for (int x = 0; x < KernelWidth; x++)
            {
                for (int y = 0; y < KernelHeight; y++)
                {
                    int weight = Kernel[y, x];
                    if (weight == 0)
                    {
                        continue;
                    }

                    int xOffset = x - (KernelWidth / 2);
                    int yOffset = y - (KernelHeight / 2);
                    int i = index + xOffset + yOffset * ImageWidth;

                    if (i >= 0 && i < PixelCount)
                    {
                        sum += GetDelta(pixels[i], center) * weight;
                    }
                }
            }

            return sum;
        }

        private static int GetDelta(Color c1, Color c2)
        {
            int sum = 0;
            sum += Math.Abs(c1.R - c2.R);
            sum += Math.Abs(c1.G - c2.G);
            sum += Math.Abs(c1.B - c2.B);
            return sum;
        }

        private static bool TryRandomSwap(Color[] pixels)
        {
            int index1 = _random.Next(PixelCount);
            int index2 = _random.Next(PixelCount);

            int delta = GetNeighborDelta(pixels, index1, index2);
            Swap(pixels, index1, index2);
            int newDelta = GetNeighborDelta(pixels, index1, index2);

            if (newDelta < delta)
            {
                return true;
            }
            else
            {
                // Swap back
                Swap(pixels, index1, index2);
                return false;
            }
        }

        static void Main(string[] args)
        {
            string fileNameFormat = "{0:D10}.png";
            var image = CreateAllColors();
            ToBitmap(image).Save("start.png");
            Shuffle(image);
            ToBitmap(image).Save(string.Format(fileNameFormat, 0));

            long generation = 0;
            while (true)
            {
                bool swapped = TryRandomSwap(image);
                if (swapped)
                {
                    generation++;
                    if (generation % 1000 == 0)
                    {
                        ToBitmap(image).Save(string.Format(fileNameFormat, generation));
                    }
                }
            }
        }
    }
}

идея

Сначала мы начнем со случайного перемешивания:

введите описание изображения здесь

Затем мы случайным образом выбираем два пикселя и меняем их местами. Если это не увеличивает сходство пикселей с их соседями, мы переключаемся назад и пытаемся снова. Мы повторяем этот процесс снова и снова.

Через несколько поколений (5000) различия не так очевидны ...

введите описание изображения здесь

Но чем дольше он работает (25000), ...

введите описание изображения здесь

... более определенные образцы начинают появляться (100000).

введите описание изображения здесь

Используя разные определения соседства , мы можем влиять на эти паттерны и на то, стабильны они или нет. Это Kernelматрица, аналогичная тем, которые используются для фильтров при обработке изображений . Он определяет веса каждого соседа, используемого для расчета дельты RGB.

Результаты

Вот некоторые из результатов, которые я создал. Видео показывают итеративный процесс (1 кадр == 1000 поколений), но, к сожалению, качество не самое лучшее (vimeo, YouTube и т. Д. Не поддерживают должным образом такие маленькие размеры). Позже я могу попытаться создать видео лучшего качества.

0 1 0
1 X 1
0 1 0

185000 поколений:

введите описание изображения здесь Видео (00:06)


0 0 1 0 0
0 2 3 2 0
1 3 X 3 1
0 2 3 2 0
0 0 1 0 0

243000 поколений:

введите описание изображения здесь Видео (00:07)


3 0 0 0 3
0 1 0 1 0
0 0 X 0 0
0 1 0 1 0
3 0 0 0 3

230000 поколений:

введите описание изображения здесь Видео (00:07)


0 0 1 0 0 0 0
0 1 2 1 0 0 0
1 2 3 X 1 0 0
0 1 2 0 0 0 0
0 0 1 0 0 0 0

Это ядро ​​интересно тем, что из-за его асимметрии паттерны нестабильны, и с течением поколений все изображение смещается вправо.

2331000 поколений:

введите описание изображения здесь Видео (01:10)


Большие результаты (512x512)

Использование вышеуказанных ядер с большим размером изображения создает те же локальные шаблоны, охватывающие большую общую площадь. Для стабилизации изображения 512x512 требуется от 1 до 2 миллионов поколений.

введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь


Хорошо, теперь давайте серьезно и создадим большие, менее локальные шаблоны с радиальным ядром 15x15:

0 0 0 0 0 1 1 1 1 1 0 0 0 0 0
0 0 0 1 1 2 2 2 2 2 1 1 0 0 0
0 0 1 2 2 3 3 3 3 3 2 2 1 0 0
0 1 2 2 3 4 4 4 4 4 3 2 2 1 0
0 1 2 3 4 4 5 5 5 4 4 3 2 1 0
1 2 3 4 4 5 6 6 6 5 4 4 3 2 1
1 2 3 4 5 6 7 7 7 6 5 4 3 2 1
1 2 3 4 5 6 7 X 7 6 5 4 3 2 1
1 2 3 4 5 6 7 7 7 6 5 4 3 2 1
1 2 3 4 4 5 6 6 6 5 4 4 3 2 1
0 1 2 3 4 4 5 5 5 4 4 3 2 1 0
0 1 2 2 3 4 4 4 4 4 3 2 2 1 0
0 0 1 2 2 3 3 3 3 3 2 2 1 0 0
0 0 0 1 1 2 2 2 2 2 1 1 0 0 0
0 0 0 0 0 1 1 1 1 1 0 0 0 0 0

Это резко увеличивает время вычислений на поколение. 1,71 миллиона поколений и 20 часов спустя:

введите описание изображения здесь


1
Чтобы добраться туда, нужно время, но конечный результат довольно приятный.
Примо

Интересное совпадение, у меня есть статья на эту же тему: nayuki.io/page/simulated-annealing-demo
Наюки

30

Ява

С несколькими вариантами моего другого ответа мы можем получить очень интересные результаты.

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;
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);

        int num = 0;
        ArrayList<Point> points = new ArrayList<>();
        for (int y = 0; y < 4096; y++) {
            for (int x = 0; x < 4096; x++) {
                points.add(new Point(x, y));
            }
        }
        Collections.sort(points, new Comparator<Point>() {

            @Override
            public int compare(Point t, Point t1) {
                int compareVal = (Integer.bitCount(t.x) + Integer.bitCount(t.y))
                        - (Integer.bitCount(t1.x) + Integer.bitCount(t1.y));
                return compareVal < 0 ? -1 : compareVal == 0 ? 0 : 1;
            }

        });
        for (Point p : points) {
            int x = p.x;
            int y = p.y;

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

Важный код здесь:

Collections.sort(points, new Comparator<Point>() {

    @Override
    public int compare(Point t, Point t1) {
        int compareVal = (Integer.bitCount(t.x) + Integer.bitCount(t.y))
                - (Integer.bitCount(t1.x) + Integer.bitCount(t1.y));
        return compareVal < 0 ? -1 : compareVal == 0 ? 0 : 1;
    }

});

Вывод (скриншот):

введите описание изображения здесь

Измените компаратор на это:

public int compare(Point t, Point t1) {
    int compareVal = (Integer.bitCount(t.x + t.y))
            - (Integer.bitCount(t1.x + t1.y));
    return compareVal < 0 ? -1 : compareVal == 0 ? 0 : 1;
}

И мы получаем это:

введите описание изображения здесь

Еще один вариант:

public int compare(Point t, Point t1) {
    int compareVal = (t.x + t.y)
            - (t1.x + t1.y);
    return compareVal < 0 ? -1 : compareVal == 0 ? 0 : 1;
}

введите описание изображения здесь

Еще один вариант (напоминает мне о клеточных автоматах):

public int compare(Point t, Point t1) {
    int compareVal = (t1.x - t.y)
            + (t.x - t1.y);
    return compareVal < 0 ? -1 : compareVal == 0 ? 0 : 1;
}

введите описание изображения здесь

Еще один другой вариант (новый личный фаворит):

public int compare(Point t, Point t1) {
    int compareVal = (Integer.bitCount(t.x ^ t.y))
            - (Integer.bitCount(t1.x ^ t1.y));
    return compareVal < 0 ? -1 : compareVal == 0 ? 0 : 1;
}

введите описание изображения здесь

Это выглядит так фрактально. XOR такой красивый, особенно крупным планом:

введите описание изображения здесь

Еще один крупный план:

введите описание изображения здесь

А теперь треугольник Серпинского, наклоненный:

public int compare(Point t, Point t1) {
    int compareVal = (Integer.bitCount(t.x | t.y))
            - (Integer.bitCount(t1.x | t1.y));
    return compareVal < 0 ? -1 : compareVal == 0 ? 0 : 1;
}

введите описание изображения здесь


8
Первое изображение выглядит как фотография процессора или памяти
Ник Т

@NickT Вот контраст памяти (согласно Google Images) для контраста: files.macbidouille.com/mbv2/news/news_05_10/25-nm-die.jpg
Джастин

4
Правильно, память такая бесформенная ... вероятно, очень многоядерный процессор: extremetech.com/wp-content/uploads/2012/07/Aubrey_Isle_die.jpg
Ник Т

1
Мне действительно нравятся эти последние. Очень глючно выглядящий, но с основной организационной структурой. Я хочу плетеный коврик в стиле XOR!
Джонатан Ван Матре

Это действительно круто; они напоминают мне сломанную аркадную игру или нет.
Джейсон С

29

Ява

На самом деле я не был уверен, как создавать 15- или 18-битные цвета, поэтому я просто отбросил младший значащий бит байта каждого канала, чтобы сделать 2 ^ 18 различных 24-битных цветов. Большая часть шума удаляется путем сортировки, но эффективное удаление шума выглядит так, как будто для этого потребуется сравнение более двух элементов одновременно, как это делает Comparator. Я попробую манипулировать с использованием более крупных ядер, но пока что это лучшее, что я смог сделать.

введите описание изображения здесь

Нажмите для HD изображения № 2

Низкое разрешение изображения № 2

import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import java.util.*;

public class ColorSpan extends JFrame{
    private int h, w = h = 512;
    private BufferedImage image = 
            new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);
    private WritableRaster raster = image.getRaster();
    private DataBufferInt dbInt = (DataBufferInt) 
            (raster.getDataBuffer());
    private int[] data = dbInt.getData();

    private JLabel imageLabel = new JLabel(new ImageIcon(image));
    private JPanel bordered = new JPanel(new BorderLayout());


    public <T> void transpose(ArrayList<T> objects){
        for(int i = 0; i < w; i++){
            for(int j = 0; j < i; j++){
                Collections.swap(objects,i+j*w,j+i*h);
            }
        }
    }

    public <T> void sortByLine(ArrayList<T> objects, Comparator<T> comp){
        for(int i = 0; i < h; i++){
            Collections.sort(objects.subList(i*w, (i+1)*w), comp);
        }
    }

    public void init(){
        ArrayList<Integer> colors = new ArrayList<Integer>();
        for(int i = 0, max = 1<<18; i < max; i++){
            int r = i>>12, g = (i>>6)&63, b = i&63;
            colors.add(((r<<16)+(g<<8)+b)<<2);
        }

        Comparator<Integer> comp1 = new Comparator<Integer>(){
            public int compare(Integer left, Integer right){
                int a = left.intValue(), b = right.intValue();

                int rA = a>>16, rB = b>>16,
                    gA = (a>>8)&255, gB = (b>>8)&255;
                /*double thA = Math.acos(gA*2d/255-1),
                        thB = Math.acos(gB*2d/255-1);*/
                double thA = Math.atan2(rA/255d-.5,gA/255d-.5),
                        thB = Math.atan2(rB/255d-.5,gB/255d-.5);
                return -Double.compare(thA,thB);
            }
        }, comp2 = new Comparator<Integer>(){
            public int compare(Integer left, Integer right){
                int a = left.intValue(), b = right.intValue();

                int rA = a>>16, rB = b>>16,
                    gA = (a>>8)&255, gB = (b>>8)&255,
                    bA = a&255, bB = b&255;
                double dA = Math.hypot(gA-rA,bA-rA),
                        dB = Math.hypot(gB-rB,bB-rB);
                return Double.compare(dA,dB);
            }
        }, comp3 = new Comparator<Integer>(){
            public int compare(Integer left, Integer right){
                int a = left.intValue(), b = right.intValue();

                int rA = a>>16, rB = b>>16,
                    gA = (a>>8)&255, gB = (b>>8)&255,
                    bA = a&255, bB = b&255;

                    return Integer.compare(rA+gA+bA,rB+gB+bB);
            }
        };

        /* Start: Image 1 */
        Collections.sort(colors, comp2);
        transpose(colors);
        sortByLine(colors,comp2);
        transpose(colors);
        sortByLine(colors,comp1);
        transpose(colors);
        sortByLine(colors,comp2);
        sortByLine(colors,comp3);
        /* End: Image 1 */

        /* Start: Image 2 */
        Collections.sort(colors, comp1);
        sortByLine(colors,comp2);

        transpose(colors);
        sortByLine(colors,comp2);
        transpose(colors);
        sortByLine(colors,comp1);
        transpose(colors);
        sortByLine(colors,comp1);
        /* End: Image 2 */

        int index = 0;
        for(Integer color : colors){
            int cInt = color.intValue();
            data[index] = cInt;
            index++;
        }

    }

    public ColorSpan(){
        super("512x512 Unique Colors");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        init();

        bordered.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
        bordered.add(imageLabel,BorderLayout.CENTER);
        add(bordered,BorderLayout.CENTER);
        pack();

    }

    public static void main(String[] args){
        new ColorSpan().setVisible(true);
    }
}

1
Этот второй действительно заслуживает наличия 24-битной версии 4096 x 4096 ...
trichoplax

Имгур обрабатывал изображение около получаса. Я предполагаю, что это, вероятно, пытается сжать это. Во всяком случае, я добавил ссылку: SSend.it/hj4ovh
Джон P

2
Есть проблема с загрузкой.
SuperJedi224

28

Scala

Я заказываю все цвета, проходя 3-мерную кривую Гильберта через L-систему . Затем я перемещаю пиксели в выходном изображении вдоль двухмерной кривой Гильберта и выкладываю все цвета.

Выход 512 х 512:

введите описание изображения здесь

Вот код Большинство из них охватывает только логику и математику перемещения через три измерения через шаг / крен / рыскание. Я уверен, что был лучший способ сделать эту часть, ну да ладно.

import scala.annotation.tailrec
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import java.io.File

object AllColors {

  case class Vector(val x: Int, val y: Int, val z: Int) {
    def applyTransformation(m: Matrix): Vector = {
      Vector(m.r1.x * x + m.r1.y * y + m.r1.z * z, m.r2.x * x + m.r2.y * y + m.r2.z * z, m.r3.x * x + m.r3.y * y + m.r3.z * z)
    }
    def +(v: Vector): Vector = {
      Vector(x + v.x, y + v.y, z + v.z)
    }
    def unary_-(): Vector = Vector(-x, -y, -z)
  }

  case class Heading(d: Vector, s: Vector) {
    def roll(positive: Boolean): Heading = {
      val (axis, b) = getAxis(d)
      Heading(d, s.applyTransformation(rotationAbout(axis, !(positive ^ b))))
    }

    def yaw(positive: Boolean): Heading = {
      val (axis, b) = getAxis(s)
      Heading(d.applyTransformation(rotationAbout(axis, positive ^ b)), s)
    }

    def pitch(positive: Boolean): Heading = {
      if (positive) {
        Heading(s, -d)
      } else {
        Heading(-s, d)
      }
    }

    def applyCommand(c: Char): Heading = c match {
      case '+' => yaw(true)
      case '-' => yaw(false)
      case '^' => pitch(true)
      case 'v' => pitch(false)
      case '>' => roll(true)
      case '<' => roll(false)
    }
  }

  def getAxis(v: Vector): (Char, Boolean) = v match {
    case Vector(1, 0, 0) => ('x', true)
    case Vector(-1, 0, 0) => ('x', false)
    case Vector(0, 1, 0) => ('y', true)
    case Vector(0, -1, 0) => ('y', false)
    case Vector(0, 0, 1) => ('z', true)
    case Vector(0, 0, -1) => ('z', false)
  }

  def rotationAbout(axis: Char, positive: Boolean) = (axis, positive) match {
    case ('x', true) => XP
    case ('x', false) => XN
    case ('y', true) => YP
    case ('y', false) => YN
    case ('z', true) => ZP
    case ('z', false) => ZN
  }

  case class Matrix(val r1: Vector, val r2: Vector, val r3: Vector)

  val ZP = Matrix(Vector(0,-1,0),Vector(1,0,0),Vector(0,0,1))
  val ZN = Matrix(Vector(0,1,0),Vector(-1,0,0),Vector(0,0,1))

  val XP = Matrix(Vector(1,0,0),Vector(0,0,-1),Vector(0,1,0))
  val XN = Matrix(Vector(1,0,0),Vector(0,0,1),Vector(0,-1,0))

  val YP = Matrix(Vector(0,0,1),Vector(0,1,0),Vector(-1,0,0))
  val YN = Matrix(Vector(0,0,-1),Vector(0,1,0),Vector(1,0,0))

  @tailrec def applyLSystem(current: Stream[Char], rules: Map[Char, List[Char]], iterations: Int): Stream[Char] = {
    if (iterations == 0) {
      current
    } else {
      val nextStep = current flatMap { c => rules.getOrElse(c, List(c)) }
      applyLSystem(nextStep, rules, iterations - 1)
    }
  }

  def walk(x: Vector, h: Heading, steps: Stream[Char]): Stream[Vector] = steps match {
    case Stream() => Stream(x)
    case 'f' #:: rest => x #:: walk(x + h.d, h, rest)
    case c #:: rest => walk(x, h.applyCommand(c), rest)
  }

  def hilbert3d(n: Int): Stream[Vector] = {
    val rules = Map('x' -> "^>x<f+>>x<<f>>x<<+fvxfxvf+>>x<<f>>x<<+f>x<^".toList)
    val steps = applyLSystem(Stream('x'), rules, n) filterNot (_ == 'x')
    walk(Vector(0, 0, 0), Heading(Vector(1, 0, 0), Vector(0, 1, 0)), steps)
  }

  def hilbert2d(n: Int): Stream[Vector] = {
    val rules = Map('a' -> "-bf+afa+fb-".toList, 'b' -> "+af-bfb-fa+".toList)
    val steps = applyLSystem(Stream('a'), rules, n) filterNot (c => c == 'a' || c == 'b')
    walk(Vector(0, 0, 0), Heading(Vector(1, 0, 0), Vector(0, 0, 1)), steps)
  }

  def main(args: Array[String]): Unit = {
    val n = 4
    val img = new BufferedImage(1 << (3 * n), 1 << (3 * n), BufferedImage.TYPE_INT_RGB)
    hilbert3d(n * 2).zip(hilbert2d(n * 3)) foreach { case (Vector(r,g,b), Vector(x,y,_)) => img.setRGB(x, y, (r << (24 - 2 * n)) | (g << (16 - 2 * n)) | (b << (8 - 2 * n))) }
    ImageIO.write(img, "png", new File(s"out_$n.png"))
  }
}

28

C #

Вау, действительно крутые вещи в этом вызове. Я попробовал сделать это в C # и сгенерировал изображение 4096x4096 примерно за 3 минуты (процессор i7), используя каждый отдельный цвет с помощью логики Random Walk.

Хорошо, так для кода. После нескольких часов исследований и попыток сгенерировать каждый отдельный цвет HSL, используя циклы for в коде, я решил создать плоский файл для чтения цветов HSL. Что я сделал, так это создал каждый RGB-цвет в Список, затем я упорядочил по Hue, Luminosity, затем Saturation. Затем я сохранил список в текстовый файл. ColorData - это небольшой класс, который я написал, который принимает цвет RGB, а также хранит эквивалент HSL. Этот код - ОГРОМНЫЙ едок RAM. Используется около 4GB RAM LOL.

public class RGB
{
    public double R = 0;
    public double G = 0;
    public double B = 0;
    public override string ToString()
    {
        return "RGB:{" + (int)R + "," + (int)G + "," + (int)B + "}";
    }
}
public class HSL
{
    public double H = 0;
    public double S = 0;
    public double L = 0;
    public override string ToString()
    {
        return "HSL:{" + H + "," + S + "," + L + "}";
    }
}
public class ColorData
{
    public RGB rgb;
    public HSL hsl;
    public ColorData(RGB _rgb)
    {
        rgb = _rgb;
        var _hsl = ColorHelper._color_rgb2hsl(new double[]{rgb.R,rgb.G,rgb.B});
        hsl = new HSL() { H = _hsl[0], S = _hsl[1], L = _hsl[2] };
    }
    public ColorData(double[] _rgb)
    {
        rgb = new RGB() { R = _rgb[0], G = _rgb[1], B = _rgb[2] };
        var _hsl = ColorHelper._color_rgb2hsl(_rgb);
        hsl = new HSL() { H = _hsl[0], S = _hsl[1], L = _hsl[2] };
    }
    public override string ToString()
    {
        return rgb.ToString() + "|" + hsl.ToString();
    }
    public int Compare(ColorData cd)
    {
        if (this.hsl.H > cd.hsl.H)
        {
            return 1;
        }
        if (this.hsl.H < cd.hsl.H)
        {
            return -1;
        }

        if (this.hsl.S > cd.hsl.S)
        {
            return 1;
        }
        if (this.hsl.S < cd.hsl.S)
        {
            return -1;
        }

        if (this.hsl.L > cd.hsl.L)
        {
            return 1;
        }
        if (this.hsl.L < cd.hsl.L)
        {
            return -1;
        }
        return 0;
    }
}
public static class ColorHelper
{


    public static void MakeColorFile(string savePath)
    {
        List<ColorData> Colors = new List<ColorData>();
        System.IO.File.Delete(savePath);

        for (int r = 0; r < 256; r++)
        {
            for (int g = 0; g < 256; g++)
            {
                for (int b = 0; b < 256; b++)
                {
                    double[] rgb = new double[] { r, g, b };
                    ColorData cd = new ColorData(rgb);
                    Colors.Add(cd);
                }
            }
        }
        Colors = Colors.OrderBy(x => x.hsl.H).ThenBy(x => x.hsl.L).ThenBy(x => x.hsl.S).ToList();

        string cS = "";
        using (System.IO.StreamWriter fs = new System.IO.StreamWriter(savePath))
        {

            foreach (var cd in Colors)
            {
                cS = cd.ToString();
                fs.WriteLine(cS);
            }
        }
    }


    public static IEnumerable<Color> NextColorHThenSThenL()
    {
        HashSet<string> used = new HashSet<string>();
        double rMax = 720;
        double gMax = 700;
        double bMax = 700;
        for (double r = 0; r <= rMax; r++)
        {
            for (double g = 0; g <= gMax; g++)
            {
                for (double b = 0; b <= bMax; b++)
                {
                    double h = (r / (double)rMax);
                    double s = (g / (double)gMax);
                    double l = (b / (double)bMax);
                    var c = _color_hsl2rgb(new double[] { h, s, l });
                    Color col = Color.FromArgb((int)c[0], (int)c[1], (int)c[2]);
                    string key = col.R + "-" + col.G + "-" + col.B;
                    if (!used.Contains(key))
                    {
                        used.Add(key);
                        yield return col;
                    }
                    else
                    {
                        continue;
                    }
                }
            }
        }
    }

    public static Color HSL2RGB(double h, double s, double l){
        double[] rgb= _color_hsl2rgb(new double[] { h, s, l });
        return Color.FromArgb((int)rgb[0], (int)rgb[1], (int)rgb[2]);
    }
    public static double[] _color_rgb2hsl(double[] rgb)
    {
        double r = rgb[0]; double g = rgb[1]; double b = rgb[2];
        double min = Math.Min(r, Math.Min(g, b));
        double max = Math.Max(r, Math.Max(g, b));
        double delta = max - min;
        double l = (min + max) / 2.0;
        double s = 0;
        if (l > 0 && l < 1)
        {
            s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l));
        }
        double h = 0;
        if (delta > 0)
        {
            if (max == r && max != g) h += (g - b) / delta;
            if (max == g && max != b) h += (2 + (b - r) / delta);
            if (max == b && max != r) h += (4 + (r - g) / delta);
            h /= 6;
        } return new double[] { h, s, l };
    }


    public static double[] _color_hsl2rgb(double[] hsl)
    {
        double h = hsl[0];
        double s = hsl[1];
        double l = hsl[2];
        double m2 = (l <= 0.5) ? l * (s + 1) : l + s - l * s;
        double m1 = l * 2 - m2;
        return new double[]{255*_color_hue2rgb(m1, m2, h + 0.33333),
           255*_color_hue2rgb(m1, m2, h),
           255*_color_hue2rgb(m1, m2, h - 0.33333)};
    }


    public static double _color_hue2rgb(double m1, double m2, double h)
    {
        h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h);
        if (h * (double)6 < 1) return m1 + (m2 - m1) * h * (double)6;
        if (h * (double)2 < 1) return m2;
        if (h * (double)3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * (double)6;
        return m1;
    }


}

С этим из пути. Я написал класс, чтобы получить следующий цвет из сгенерированного файла. Позволяет установить начало оттенка и конец оттенка. На самом деле, это можно и нужно, вероятно, обобщать в зависимости от размера файла, который был отсортирован первым. Также я понимаю, что для повышения производительности я мог бы просто поместить значения RGB в файл и оставить каждую строку фиксированной длины. Таким образом, я мог бы легко указать смещение в байтах вместо того, чтобы проходить по каждой строке, пока не достигну строки, с которой хотел начать. Но это было не так уж сильно для меня. Но вот этот класс

public class HSLGenerator
{

    double hEnd = 1;
    double hStart = 0;

    double colCount = 256 * 256 * 256;

    public static Color ReadRGBColorFromLine(string line)
    {
        string sp1 = line.Split(new string[] { "RGB:{" }, StringSplitOptions.None)[1];
        string sp2 = sp1.Split('}')[0];
        string[] sp3 = sp2.Split(',');
        return Color.FromArgb(Convert.ToInt32(sp3[0]), Convert.ToInt32(sp3[1]), Convert.ToInt32(sp3[2]));
    }
    public IEnumerable<Color> GetNextFromFile(string colorFile)
    {
        int currentLine = -1;
        int startLine = Convert.ToInt32(hStart * colCount);
        int endLine = Convert.ToInt32(hEnd * colCount);
        string line = "";
        using(System.IO.StreamReader sr = new System.IO.StreamReader(colorFile))
        {

            while (!sr.EndOfStream)
            {
                line = sr.ReadLine();
                currentLine++;
                if (currentLine < startLine) //begin at correct offset
                {
                    continue;
                }
                yield return ReadRGBColorFromLine(line);
                if (currentLine > endLine) 
                {
                    break;
                }
            }
    }

    HashSet<string> used = new HashSet<string>();

    public void SetHueLimits(double hueStart, double hueEnd)
    {
        hEnd = hueEnd;
        hStart = hueStart;
    }
}

Итак, теперь, когда у нас есть файл цвета, и у нас есть способ прочитать файл, мы теперь можем фактически сделать изображение. Я использовал найденный класс для повышения производительности установки пикселей в растровом изображении, который называется LockBitmap.Источник LockBitmap

Я создал небольшой класс Vector2 для хранения координатных местоположений

public class Vector2
{
    public int X = 0;
    public int Y = 0;
    public Vector2(int x, int y)
    {
        X = x;
        Y = y;
    }
    public Vector2 Center()
    {
        return new Vector2(X / 2, Y / 2);
    }
    public override string ToString()
    {
        return X.ToString() + "-" + Y.ToString();
    }
}

И я также создал класс с именем SearchArea, который был полезен для поиска соседних пикселей. Вы указываете пиксель, для которого хотите найти соседей, границы для поиска и размер «соседнего квадрата» для поиска. Поэтому, если размер равен 3, это означает, что вы ищете квадрат 3х3 с указанным пикселем прямо в центре.

public class SearchArea
{
    public int Size = 0;
    public Vector2 Center;
    public Rectangle Bounds;

    public SearchArea(int size, Vector2 center, Rectangle bounds)
    {
        Center = center;
        Size = size;
        Bounds = bounds;
    }
    public bool IsCoordinateInBounds(int x, int y)
    {
        if (!IsXValueInBounds(x)) { return false; }
        if (!IsYValueInBounds(y)) { return false; }
        return true;

    }
    public bool IsXValueInBounds(int x)
    {
        if (x < Bounds.Left || x >= Bounds.Right) { return false; }
        return true;
    }
    public bool IsYValueInBounds(int y)
    {
        if (y < Bounds.Top || y >= Bounds.Bottom) { return false; }
        return true;
    }

}

Вот класс, который на самом деле выбирает следующего соседа. В основном есть 2 режима поиска. А) Полный квадрат, Б) просто периметр квадрата. Это была оптимизация, которую я сделал, чтобы предотвратить повторный поиск по всему квадрату после того, как понял, что квадрат заполнен. DepthMap - это еще одна оптимизация для предотвращения повторного поиска одних и тех же квадратов. Однако я не полностью оптимизировал это. Каждый вызов GetNeighbors всегда сначала выполняет полный поиск по квадрату. Я знаю, что могу оптимизировать это, чтобы выполнять поиск по периметру только после завершения начального полного квадрата. Я просто еще не дошел до этой оптимизации, и даже без нее код довольно быстрый. Закомментированные строки «lock» объясняются тем, что в какой-то момент я использовал Parallel.ForEach, но понял, что для этого мне нужно было написать больше кода, чем я хотел.

public class RandomWalkGenerator
{
    HashSet<string> Visited = new HashSet<string>();
    Dictionary<string, int> DepthMap = new Dictionary<string, int>();
    Rectangle Bounds;
    Random rnd = new Random();
    public int DefaultSearchSize = 3;
    public RandomWalkGenerator(Rectangle bounds)
    {
        Bounds = bounds;
    }
    private SearchArea GetSearchArea(Vector2 center, int size)
    {
        return new SearchArea(size, center, Bounds);
    }

    private List<Vector2> GetNeighborsFullSearch(SearchArea srchArea, Vector2 coord)
    {
        int radius = (int)Math.Floor((double)((double)srchArea.Size / (double)2));
        List<Vector2> pixels = new List<Vector2>();
        for (int rX = -radius; rX <= radius; rX++)
        {
            for (int rY = -radius; rY <= radius; rY++)
            {
                if (rX == 0 && rY == 0) { continue; } //not a new coordinate
                int x = rX + coord.X;
                int y = rY + coord.Y;
                if (!srchArea.IsCoordinateInBounds(x, y)) { continue; }
                var key = x + "-" + y;
                // lock (Visited)
                {
                    if (!Visited.Contains(key))
                    {
                        pixels.Add(new Vector2(x, y));
                    }
                }
            }
        }
        if (pixels.Count == 0)
        {
            int depth = 0;
            string vecKey = coord.ToString();
            if (!DepthMap.ContainsKey(vecKey))
            {
                DepthMap.Add(vecKey, depth);
            }
            else
            {
                depth = DepthMap[vecKey];
            }

            var size = DefaultSearchSize + 2 * depth;
            var sA = GetSearchArea(coord, size);
            pixels = GetNeighborsPerimeterSearch(sA, coord, depth);
        }
        return pixels;
    }
    private Rectangle GetBoundsForPerimeterSearch(SearchArea srchArea, Vector2 coord)
    {
        int radius = (int)Math.Floor((decimal)(srchArea.Size / 2));
        Rectangle r = new Rectangle(-radius + coord.X, -radius + coord.Y, srchArea.Size, srchArea.Size);
        return r;
    }
    private List<Vector2> GetNeighborsPerimeterSearch(SearchArea srchArea, Vector2 coord, int depth = 0)
    {
        string vecKey = coord.ToString();
        if (!DepthMap.ContainsKey(vecKey))
        {
            DepthMap.Add(vecKey, depth);
        }
        else
        {
            DepthMap[vecKey] = depth;
        }
        Rectangle bounds = GetBoundsForPerimeterSearch(srchArea, coord);
        List<Vector2> pixels = new List<Vector2>();
        int depthMax = 1500;

        if (depth > depthMax)
        {
            return pixels;
        }

        int yTop = bounds.Top;
        int yBot = bounds.Bottom;

        //left to right scan
        for (int x = bounds.Left; x < bounds.Right; x++)
        {

            if (srchArea.IsCoordinateInBounds(x, yTop))
            {
                var key = x + "-" + yTop;
                // lock (Visited)
                {
                    if (!Visited.Contains(key))
                    {
                        pixels.Add(new Vector2(x, yTop));
                    }
                }
            }
            if (srchArea.IsCoordinateInBounds(x, yBot))
            {
                var key = x + "-" + yBot;
                // lock (Visited)
                {
                    if (!Visited.Contains(key))
                    {
                        pixels.Add(new Vector2(x, yBot));
                    }
                }
            }
        }

        int xLeft = bounds.Left;
        int xRight = bounds.Right;
        int yMin = bounds.Top + 1;
        int yMax = bounds.Bottom - 1;
        //top to bottom scan
        for (int y = yMin; y < yMax; y++)
        {
            if (srchArea.IsCoordinateInBounds(xLeft, y))
            {
                var key = xLeft + "-" + y;
                // lock (Visited)
                {
                    if (!Visited.Contains(key))
                    {
                        pixels.Add(new Vector2(xLeft, y));
                    }
                }
            }
            if (srchArea.IsCoordinateInBounds(xRight, y))
            {
                var key = xRight + "-" + y;
                // lock (Visited)
                {
                    if (!Visited.Contains(key))
                    {
                        pixels.Add(new Vector2(xRight, y));
                    }
                }
            }
        }

        if (pixels.Count == 0)
        {
            var size = srchArea.Size + 2;
            var sA = GetSearchArea(coord, size);
            pixels = GetNeighborsPerimeterSearch(sA, coord, depth + 1);
        }
        return pixels;
    }
    private List<Vector2> GetNeighbors(SearchArea srchArea, Vector2 coord)
    {
        return GetNeighborsFullSearch(srchArea, coord);
    }
    public Vector2 ChooseNextNeighbor(Vector2 coord)
    {
        SearchArea sA = GetSearchArea(coord, DefaultSearchSize);
        List<Vector2> neighbors = GetNeighbors(sA, coord);
        if (neighbors.Count == 0)
        {
            return null;
        }
        int idx = rnd.Next(0, neighbors.Count);
        Vector2 elm = neighbors.ElementAt(idx);
        string key = elm.ToString();
        // lock (Visited)
        {
            Visited.Add(key);
        }
        return elm;
    }
}

Хорошо, отлично, теперь вот класс, который создает изображение

public class RandomWalk
{
    Rectangle Bounds;
    Vector2 StartPath = new Vector2(0, 0);
    LockBitmap LockMap;
    RandomWalkGenerator rwg;
    public int RandomWalkSegments = 1;
    string colorFile = "";

    public RandomWalk(int size, string _colorFile)
    {
        colorFile = _colorFile;
        Bounds = new Rectangle(0, 0, size, size);
        rwg = new RandomWalkGenerator(Bounds);
    }
    private void Reset()
    {
        rwg = new RandomWalkGenerator(Bounds);
    }
    public void CreateImage(string savePath)
    {
        Reset();
        Bitmap bmp = new Bitmap(Bounds.Width, Bounds.Height);
        LockMap = new LockBitmap(bmp);
        LockMap.LockBits();
        if (RandomWalkSegments == 1)
        {
            RandomWalkSingle();
        }
        else
        {
            RandomWalkMulti(RandomWalkSegments);
        }
        LockMap.UnlockBits();
        bmp.Save(savePath);

    }
    public void SetStartPath(int X, int Y)
    {
        StartPath.X = X;
        StartPath.Y = Y;
    }
    private void RandomWalkMulti(int buckets)
    {

        int Buckets = buckets;
        int PathsPerSide = (Buckets + 4) / 4;
        List<Vector2> Positions = new List<Vector2>();

        var w = Bounds.Width;
        var h = Bounds.Height;
        var wInc = w / Math.Max((PathsPerSide - 1),1);
        var hInc = h / Math.Max((PathsPerSide - 1),1);

        //top
        for (int i = 0; i < PathsPerSide; i++)
        {
            var x = Math.Min(Bounds.Left + wInc * i, Bounds.Right - 1);
            Positions.Add(new Vector2(x, Bounds.Top));
        }
        //bottom
        for (int i = 0; i < PathsPerSide; i++)
        {
            var x = Math.Max(Bounds.Right -1 - wInc * i, 0);
            Positions.Add(new Vector2(x, Bounds.Bottom - 1));
        }
        //right and left
        for (int i = 1; i < PathsPerSide - 1; i++)
        {
            var y = Math.Min(Bounds.Top + hInc * i, Bounds.Bottom - 1);
            Positions.Add(new Vector2(Bounds.Left, y));
            Positions.Add(new Vector2(Bounds.Right - 1, y));
        }
        Positions = Positions.OrderBy(x => Math.Atan2(x.X, x.Y)).ToList();
        double cnt = 0;
        List<IEnumerator<bool>> _execs = new List<IEnumerator<bool>>();
        foreach (Vector2 startPath in Positions)
        {
            double pct = cnt / (Positions.Count);
            double pctNext = (cnt + 1) / (Positions.Count);

            var enumer = RandomWalkHueSegment(pct, pctNext, startPath).GetEnumerator();

            _execs.Add(enumer);
            cnt++;
        }

        bool hadChange = true;
        while (hadChange)
        {
            hadChange = false;
            foreach (var e in _execs)
            {
                if (e.MoveNext())
                {
                    hadChange = true;
                }
            }
        }

    }
    private IEnumerable<bool> RandomWalkHueSegment(double hueStart, double hueEnd, Vector2 startPath)
    {
        var colors = new HSLGenerator();
        colors.SetHueLimits(hueStart, hueEnd);
        var colorFileEnum = colors.GetNextFromFile(colorFile).GetEnumerator();
        Vector2 coord = new Vector2(startPath.X, startPath.Y);
        LockMap.SetPixel(coord.X, coord.Y, ColorHelper.HSL2RGB(0, 0, 0));

        while (true)
        {
            if (!colorFileEnum.MoveNext())
            {
                break;
            }
            var rgb = colorFileEnum.Current;
            coord = ChooseNextNeighbor(coord);
            if (coord == null)
            {
                break;
            }
            LockMap.SetPixel(coord.X, coord.Y, rgb);
            yield return true;

        }
    }
    private void RandomWalkSingle()
    {
        Vector2 coord = new Vector2(StartPath.X, StartPath.Y);
        LockMap.SetPixel(coord.X, coord.Y, ColorHelper.HSL2RGB(0, 0, 0));
        int cnt = 1;
        var colors = new HSLGenerator();
        var colorFileEnum = colors.GetNextFromFile(colorFile).GetEnumerator();
        while (true)
        {
            if (!colorFileEnum.MoveNext())
            {
                return;
            }
            var rgb = colorFileEnum.Current;
            var newCoord = ChooseNextNeighbor(coord);
            coord = newCoord;
            if (newCoord == null)
            {
                return;
            }
            LockMap.SetPixel(newCoord.X, newCoord.Y, rgb);
            cnt++;

        }

    }

    private Vector2 ChooseNextNeighbor(Vector2 coord)
    {
        return rwg.ChooseNextNeighbor(coord);
    }


}

А вот пример реализации:

class Program
{
    static void Main(string[] args)
    {
        {
           // ColorHelper.MakeColorFile();
          //  return;
        }
        string colorFile = "colors.txt";
        var size = new Vector2(1000,1000);
        var ctr = size.Center();
        RandomWalk r = new RandomWalk(size.X,colorFile);
        r.RandomWalkSegments = 8;
        r.SetStartPath(ctr.X, ctr.Y);
        r.CreateImage("test.bmp");

    }
}

Если RandomWalkSegments = 1, то он в основном просто начинает ходить, куда бы вы ни сказали, и начинается с первого первого цвета в файле.

Признаюсь, это не самый чистый код, но он работает довольно быстро!

Обрезанный вывод

3 Пути

128 путей

РЕДАКТИРОВАТЬ:

Итак, я изучал OpenGL и шейдеры. Я сгенерировал 4096x4096, используя каждый сверкающий цвет на GPU с двумя простыми шейдерными скриптами. Вывод скучный, но подумал, что кто-то может найти это интересным и придумать несколько классных идей:

Вершинный шейдер

attribute vec3 a_position;
varying vec2 vTexCoord;
   void main() {
      vTexCoord = (a_position.xy + 1) / 2;
      gl_Position = vec4(a_position, 1);
  }

Frag Shader

void main(void){
    int num = int(gl_FragCoord.x*4096.0 + gl_FragCoord.y);
    int h = num % 256;
    int s = (num/256) % 256;
    int l = ((num/256)/256) % 256;
    vec4 hsl = vec4(h/255.0,s/255.0,l/255.0,1.0);
    gl_FragColor = hsl_to_rgb(hsl); // you need to implement a conversion method
}

Редактировать (15.10.16): Просто хотел показать доказательство концепции генетического алгоритма. Я ПОСТОЯННО запускаю этот код через 24 часа на наборе случайных цветов 100x100, но пока результат получается прекрасным!введите описание изображения здесь

Редактирование (26.10.16): я уже 12 дней выполняю код генетического алгоритма ... и он все еще оптимизирует вывод. Он в основном приблизился к какому-то локальному минимуму, но, по-видимому, еще больше улучшается:введите описание изображения здесь

Редактировать: 12/12/17 - я написал новый алгоритм случайного блуждания - в основном вы указываете количество «ходящих», но вместо случайного блуждания - они случайным образом выбирают другого бродяги и либо избегают их (выбирают следующий доступный пиксель дальше всего) ) - или идите к ним (выберите следующий доступный пиксель, ближайший к ним). Пример вывода в градациях серого приведен здесь (я буду делать полную визуализацию 4096x4096 цветов после того, как подключу раскраску!):введите описание изображения здесь


4
Немного запоздало, но добро пожаловать в PPCG! Это отличный первый пост.
спагетто

1
Спасибо! Я с нетерпением жду завершения большего количества проблем! В последнее время я больше занимаюсь кодированием изображений, это мое новое хобби
applejacks01

Ух ты, они потрясающие; Я рад, что вернулся сегодня на этот пост и проверил все последующие вещи.
Джейсон С

Спасибо! На самом деле я сейчас занимаюсь кодированием генетического алгоритма, чтобы получить интересные градиенты. В основном, беру 10000 цветов, образуя сетку 100х100. Для каждого пикселя получите соседние пиксели. Для каждого получите расстояние CIEDE2000. Подведите итог. Суммируйте это для всех 10000 пикселей. Генетический алгоритм пытается уменьшить эту общую сумму. Это медленно, но для изображения
20x20

Я особенно люблю вывод этого решения.
r_alex_hall

22

HTML5 холст + JavaScript

Я называю это randoGraph и вы можете создать столько , сколько вы хотите здесь

Некоторые примеры:

пример 1

пример 2

пример 3

пример 4

пример 5

пример 6

пример 7

Например, в Firefox вы можете щелкнуть правой кнопкой мыши на холсте (когда он закончится) и сохранить его как изображение. Создание изображения 4096x4096 представляет собой проблему из-за ограничения памяти в некоторых браузерах.

Идея довольно проста, но каждое изображение уникально. Сначала мы создаем палитру цветов. Затем, начиная с X точек, мы выбираем случайные цвета из палитры и положения для них (каждый раз, когда мы выбираем цвет, мы удаляем его из палитры), и мы записываем, где мы его поместили, чтобы не помещать в ту же позицию следующий пиксель.

Для каждого пикселя, который касается этого, мы создаем число (X) возможных цветов, а затем выбираем наиболее соответствующий этому пикселю. Это продолжается до тех пор, пока изображение не будет завершено.

HTML код

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="el">
<head>
<script type="text/javascript" src="randoGraph.js"></script>
</head>
<body>
    <canvas id="randoGraphCanvas"></canvas> 
</body>
</html>

И JavaScript для randoGraph.js

window.onload=function(){
    randoGraphInstance = new randoGraph("randoGraphCanvas",256,128,1,1);
    randoGraphInstance.setRandomness(500, 0.30, 0.11, 0.59);
    randoGraphInstance.setProccesses(10);
    randoGraphInstance.init(); 
}

function randoGraph(canvasId,width,height,delay,startings)
{
    this.pixels = new Array();
    this.colors = new Array(); 
    this.timeouts = new Array(); 
    this.randomFactor = 500;
    this.redFactor = 0.30;
    this.blueFactor = 0.11;
    this.greenFactor  = 0.59;
    this.processes = 1;
    this.canvas = document.getElementById(canvasId); 
    this.pixelsIn = new Array(); 
    this.stopped = false;

    this.canvas.width = width;
    this.canvas.height = height;
    this.context = this.canvas.getContext("2d");
    this.context.clearRect(0,0, width-1 , height-1);
    this.shadesPerColor = Math.pow(width * height, 1/3);
    this.shadesPerColor = Math.round(this.shadesPerColor * 1000) / 1000;

    this.setRandomness = function(randomFactor,redFactor,blueFactor,greenFactor)
    {
        this.randomFactor = randomFactor;
        this.redFactor = redFactor;
        this.blueFactor = blueFactor;
        this.greenFactor = greenFactor;
    }

    this.setProccesses = function(processes)
    {
        this.processes = processes;
    }

    this.init = function()
    {
        if(this.shadesPerColor > 256 || this.shadesPerColor % 1 > 0) 
        { 
            alert("The dimensions of the image requested to generate are invalid. The product of width multiplied by height must be a cube root of a integer number up to 256."); 
        }
        else 
        {
            var steps = 256 / this.shadesPerColor;
            for(red = steps / 2; red <= 255;)
            {
                for(blue = steps / 2; blue <= 255;)
                {
                    for(green = steps / 2; green <= 255;)
                    {   
                        this.colors.push(new Color(Math.round(red),Math.round(blue),Math.round(green)));
                        green = green + steps;
                    }
                    blue = blue + steps; 
                }
                red = red + steps; 
            }   

            for(var i = 0; i < startings; i++)
            {
                var color = this.colors.splice(randInt(0,this.colors.length - 1),1)[0];
                var pixel = new Pixel(randInt(0,width - 1),randInt(0,height - 1),color);
                this.addPixel(pixel);       
            }

            for(var i = 0; i < this.processes; i++)
            {
                this.timeouts.push(null);
                this.proceed(i);
            }
        }
    }

    this.proceed = function(index) 
    { 
        if(this.pixels.length > 0)
        {
            this.proceedPixel(this.pixels.splice(randInt(0,this.pixels.length - 1),1)[0]);
            this.timeouts[index] = setTimeout(function(that){ if(!that.stopped) { that.proceed(); } },this.delay,this);
        }
    }

    this.proceedPixel = function(pixel)
    {
        for(var nx = pixel.getX() - 1; nx < pixel.getX() + 2; nx++)
        {
            for(var ny = pixel.getY() - 1; ny < pixel.getY() + 2; ny++)
            {
                if(! (this.pixelsIn[nx + "x" + ny] == 1 || ny < 0 || nx < 0 || nx > width - 1 || ny > height - 1 || (nx == pixel.getX() && ny == pixel.getY())) )
                {
                    var color = this.selectRelevantColor(pixel.getColor());
                    var newPixel = new Pixel(nx,ny,color);
                    this.addPixel(newPixel);
                }
            }
        }   
    }

    this.selectRelevantColor = function(color)
    {
        var relevancies = new Array(); 
        var relColors = new Array(); 
        for(var i = 0; i < this.randomFactor && i < this.colors.length; i++)
        {
            var index = randInt(0,this.colors.length - 1);
            var c = this.colors[index];
            var relevancy = Math.pow( ((c.getRed()-color.getRed()) * this.redFactor) , 2)
            + Math.pow( ((c.getBlue()-color.getBlue()) * this.blueFactor), 2)
            + Math.pow( ((c.getGreen()-color.getGreen()) * this.greenFactor) , 2);
            relevancies.push(relevancy); 
            relColors[relevancy+"Color"] = index;
        }
        return this.colors.splice(relColors[relevancies.min()+"Color"],1)[0]
    }

    this.addPixel = function(pixel)
    {
        this.pixels.push(pixel);
        this.pixelsIn[pixel.getX() + "x" + pixel.getY() ] = 1;
        var color = pixel.getColor();
        this.context.fillStyle = "rgb("+color.getRed()+","+color.getBlue()+","+color.getGreen()+")";
        this.context.fillRect( pixel.getX(), pixel.getY(), 1, 1);   
    }

    var toHex = function toHex(num) 
    {
        num = Math.round(num);
        var hex = num.toString(16);
        return hex.length == 1 ? "0" + hex : hex;
    }

    this.clear = function()
    {
        this.stopped = true;
    }
}

function Color(red,blue,green)
{   
    this.getRed = function() { return red; } 
    this.getBlue = function() { return blue; } 
    this.getGreen = function() { return green; } 
}

function Pixel(x,y,color)
{   
    this.getX = function() { return x; } 
    this.getY = function() { return y; } 
    this.getColor = function() { return color; } 
}


function randInt(min, max) 
{
    return Math.floor(Math.random() * (max - min + 1)) + min;
}


// @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/min
Array.prototype.min = function() 
{
      return Math.min.apply(null, this);
};

// @see http://stackoverflow.com/questions/5223/length-of-javascript-object-ie-associative-array
Object.size = function(obj) 
{
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) size++;
    }
    return size;
};

Это хорошо, но похоже на ответ C # от fejesjoco . Это случайно?
AL

1
Алгоритмы здесь, и каждый может прочитать и понять, что они действительно разные. Этот ответ, опубликованный после того, как ответ на C # от fejesjoco объявлен победителем, мотивирован тем, насколько хорош его результат. Тогда я подумал о совершенно ином подходе к обработке и выбору соседних цветов, и на этом все. Конечно, оба ответа имеют одинаковую основу, например, равномерное распределение цветов, используемых вдоль видимого спектра, концепцию соответствующих цветов и начальные точки, возможно, следуя этой основе, кто-то может подумать, что полученные изображения имеют сходство в некоторых случаях.
konstantinosX

Хорошо, извините, если вы подумали, что я критикую ваш ответ. Мне просто интересно, вдохновился ли ты ответом fejesjoco, поскольку полученный результат выглядит схожим.
AL

1
«Определение методов класса внутри конструктора вместо использования цепочки прототипов действительно неэффективно, особенно если этот класс используется несколько раз», - очень интересный комментарий Патрик Робертс. У вас есть ссылка с примером, который подтверждает это? Я искренне хотел бы знать, имеет ли это требование какое-либо основание (чтобы прекратить использовать это), и что это.
konstantinosX

2
Что касается использования прототипа: он работает почти так же, как статический метод. Когда у вас есть функция, определенная в литерале объекта, каждый новый объект, который вы создаете, должен также создать новую копию функции и сохранить их вместе с этим экземпляром объекта (таким образом, 16 миллионов цветных объектов означают 16 миллионов копий той же самой функции в Память). Для сравнения, использование прототипа создаст его только один раз, чтобы связать его с «классом», а не с объектом. Это имеет очевидные преимущества памяти, а также потенциальные преимущества скорости.
Mwr247

20

питон

Итак, вот мое решение на python, на его создание уходит почти час, поэтому, возможно, потребуется некоторая оптимизация:

import PIL.Image as Image
from random import shuffle
import math

def mulColor(color, factor):
    return (int(color[0]*factor), int(color[1]*factor), int(color[2]*factor))

def makeAllColors(arg):
    colors = []
    for r in range(0, arg):
        for g in range(0, arg):
            for b in range(0, arg):
                colors.append((r, g, b))
    return colors

def distance(color1, color2):
    return math.sqrt(pow(color2[0]-color1[0], 2) + pow(color2[1]-color1[1], 2) + pow(color2[2]-color1[2], 2))

def getClosestColor(to, colors):
    closestColor = colors[0]
    d = distance(to, closestColor)
    for color in colors:
        if distance(to, color) < d:
            closestColor = color
            d = distance(to, closestColor)
    return closestColor

imgsize = (256, 128)
#imgsize = (10, 10)
colors = makeAllColors(32)
shuffle(colors)
factor = 255.0/32.0
img = Image.new("RGB", imgsize, "white")
#start = (imgsize[0]/4, imgsize[1]/4)
start = (imgsize[0]/2, 0)
startColor = colors.pop()
img.putpixel(start, mulColor(startColor, factor))

#color = getClosestColor(startColor, colors)
#img.putpixel((start[0]+1, start[1]), mulColor(color, factor))

edgePixels = [(start, startColor)]
donePositions = [start]
for pixel in edgePixels:
    if len(colors) > 0:
        color = getClosestColor(pixel[1], colors)
    m = [(pixel[0][0]-1, pixel[0][1]), (pixel[0][0]+1, pixel[0][2]), (pixel[0][0], pixel[0][3]-1), (pixel[0][0], pixel[0][4]+1)]
    if len(donePositions) >= imgsize[0]*imgsize[1]:
    #if len(donePositions) >= 100:
        break
    for pos in m:
        if (not pos in donePositions):
            if not (pos[0]<0 or pos[1]<0 or pos[0]>=img.size[0] or pos[1]>=img.size[1]):
                img.putpixel(pos, mulColor(color, factor))
                #print(color)
                donePositions.append(pos)
                edgePixels.append((pos, color))
                colors.remove(color)
                if len(colors) > 0:
                    color = getClosestColor(pixel[1], colors)
    print((len(donePositions) * 1.0) / (imgsize[0]*imgsize[1]))
print len(donePositions)
img.save("colors.png")

Вот несколько примеров выходных данных:

введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь


1
Похоже, некоторые сумасшедшие звуковые волны
Марк Jeronimus

19

Ява

Я решил попробовать этот вызов. Я был вдохновлен этим ответом на другой код гольф. Моя программа генерирует более уродливые изображения, но они имеют все цвета.

Кроме того, мой первый раз код игры в гольф. :)

(Изображения 4k были слишком большими для моей малой скорости загрузки, я пытался загрузить одно, но через час оно не было загружено. Вы можете создать свое собственное.)

Крупный план:

Создает изображение за 70 секунд на моей машине, занимает около 1,5 ГБ памяти при генерации

Main.java

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;

import javax.imageio.ImageIO;


public class Main {
    static char[][] colors = new char[4096 * 4096][3];
    static short[][] pixels = new short[4096 * 4096][2];

    static short[][] iterMap = new short[4096][4096];  

    public static int mandel(double re0, double im0, int MAX_ITERS) {
        double re = re0;
        double im = im0;
        double _r;
        double _i;
        double re2;
        double im2;
        for (int iters = 0; iters < MAX_ITERS; iters++) {
            re2 = re * re;
            im2 = im * im;
            if (re2 + im2 > 4.0) {
                return iters;
            }
            _r = re;
            _i = im;
            _r = re2 - im2;
            _i = 2 * (re * im);
            _r += re0;
            _i += im0;
            re = _r;
            im = _i;
        }
        return MAX_ITERS;
    }

    static void shuffleArray(Object[] ar) {
        Random rnd = new Random();
        for (int i = ar.length - 1; i > 0; i--) {
          int index = rnd.nextInt(i + 1);
          // Simple swap
          Object a = ar[index];
          ar[index] = ar[i];
          ar[i] = a;
        }
      }

    public static void main(String[] args) {
        long startTime = System.nanoTime();

        System.out.println("Generating colors...");

        for (int i = 0; i < 4096 * 4096; i++) {
            colors[i][0] = (char)((i >> 16) & 0xFF); // Red
            colors[i][1] = (char)((i >> 8) & 0xFF);  // Green
            colors[i][2] = (char)(i & 0xFF);         // Blue
        }

        System.out.println("Sorting colors...");

        //shuffleArray(colors); // Not needed

        Arrays.sort(colors, new Comparator<char[]>() {
            @Override
            public int compare(char[] a, char[] b) {
                return (a[0] + a[1] + a[2]) - (b[0] + b[1] + b[2]);
            }
        });

        System.out.println("Generating fractal...");

        for (int y = -2048; y < 2048; y++) {
            for (int x = -2048; x < 2048; x++) {
                short iters = (short) mandel(x / 1024.0, y / 1024.0, 1024);
                iterMap[x + 2048][y + 2048] = iters;
            }
        }

        System.out.println("Organizing pixels in the image...");

        for (short x = 0; x < 4096; x++) {
            for (short y = 0; y < 4096; y++) {
                pixels[x * 4096 + y][0] = x;
                pixels[x * 4096 + y][1] = y;
            }
        }

        shuffleArray(pixels);

        Arrays.sort(pixels, new Comparator<short[]>() {
            @Override
            public int compare(short[] a, short[] b) {
                return iterMap[b[0]][b[1]] - iterMap[a[0]][a[1]];
            }
        });

        System.out.println("Writing image to BufferedImage...");

        BufferedImage img = new BufferedImage(4096, 4096, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = img.createGraphics();

        for (int i = 0; i < 4096 * 4096; i++) {
            g.setColor(new Color(colors[i][0], colors[i][1], colors[i][2]));
            g.fillRect(pixels[i][0], pixels[i][1], 1, 1);
        }

        g.dispose();

        System.out.println("Writing image to file...");

        File imageFile = new File("image.png");

        try {
            ImageIO.write(img, "png", imageFile);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.out.println("Done!");
        System.out.println("Took " + ((System.nanoTime() - startTime) / 1000000000.) + " seconds.");
        System.out.println();
        System.out.println("The result is saved in " + imageFile.getAbsolutePath());

    }

}

18

Mathematica

colors = Table[
r = y*256 + x; {BitAnd[r, 2^^111110000000000]/32768., 
BitAnd[r, 2^^1111100000]/1024., BitAnd[r, 2^^11111]/32.}, {y, 0, 
127}, {x, 0, 255}];
SeedRandom[1337];
maxi = 5000000;
Monitor[For[i = 0, i < maxi, i++,
x1 = RandomInteger[{2, 255}];
x2 = RandomInteger[{2, 255}];
y1 = RandomInteger[{2, 127}];
y2 = RandomInteger[{2, 127}];
c1 = colors[[y1, x1]];
c2 = colors[[y2, x2]];
ca1 = (colors[[y1 - 1, x1]] + colors[[y1, x1 - 1]] + 
  colors[[y1 + 1, x1]] + colors[[y1, x1 + 1]])/4.;
ca2 = (colors[[y2 - 1, x2]] + colors[[y2, x2 - 1]] + 
  colors[[y2 + 1, x2]] + colors[[y2, x2 + 1]])/4.;
d1 = Abs[c1[[1]] - ca1[[1]]] + Abs[c1[[2]] - ca1[[2]]] + 
Abs[c1[[3]] - ca1[[3]]];
d1p = Abs[c2[[1]] - ca1[[1]]] + Abs[c2[[2]] - ca1[[2]]] + 
Abs[c2[[3]] - ca1[[3]]];
d2 = Abs[c2[[1]] - ca2[[1]]] + Abs[c2[[2]] - ca2[[2]]] + 
Abs[c2[[3]] - ca2[[3]]];
d2p = Abs[c1[[1]] - ca2[[1]]] + Abs[c1[[2]] - ca2[[2]]] + 
Abs[c1[[3]] - ca2[[3]]];
If[(d1p + d2p < 
  d1 + d2) || (RandomReal[{0, 1}] < 
   Exp[-Log10[i]*(d1p + d2p - (d1 + d2))] && i < 1000000),
temp = colors[[y1, x1]];
colors[[y1, x1]] = colors[[y2, x2]];
colors[[y2, x2]] = temp
]
], ProgressIndicator[i, {1, maxi}]]
Image[colors]

Результат (2х):

256x128 2x

Оригинальное изображение 256x128

Редактировать:

заменив Log10 [i] на Log10 [i] / 5, вы получите: введите описание изображения здесь

Приведенный выше код относится к моделируемому отжигу. При таком взгляде второе изображение создается с более высокой «температурой» на первых 10 ^ 6 шагах. Более высокая «температура» вызывает больше перестановок среди пикселей, тогда как на первом изображении структура упорядоченного изображения все еще слабо видна.


17

JavaScript

Я все еще студент, и я впервые публикую посты, так что мои коды, вероятно, грязные, и я не уверен на 100%, что мои фотографии имеют все необходимые цвета, но я был очень доволен своими результатами, поэтому я решил опубликовать их.

Я знаю, что конкурс окончен, но мне очень понравились его результаты, и мне всегда нравился вид лабиринтов, созданных с помощью рекурсивного отслеживания, поэтому я подумал, что было бы здорово посмотреть, как будет выглядеть, если в него будут помещены цветные пиксели. Итак, я начинаю с генерации всех цветов в массиве, затем выполняю рекурсивный возврат, одновременно выталкивая цвета из массива.

Вот мой JSFiddle http://jsfiddle.net/Kuligoawesome/3VsCu/

// Global variables
const FPS = 60;// FrameRate
var canvas = null;
var ctx = null;

var bInstantDraw = false;
var MOVES_PER_UPDATE = 50; //How many pixels get placed down
var bDone = false;
var width; //canvas width
var height; //canvas height
var colorSteps = 32;

var imageData;
var grid;
var colors;

var currentPos;
var prevPositions;

// This is called when the page loads
function Init()
{
    canvas = document.getElementById('canvas'); // Get the HTML element with the ID of 'canvas'
    width = canvas.width;
    height = canvas.height;
    ctx = canvas.getContext('2d'); // This is necessary, but I don't know exactly what it does

    imageData = ctx.createImageData(width,height); //Needed to do pixel manipulation

    grid = []; //Grid for the labyrinth algorithm
    colors = []; //Array of all colors
    prevPositions = []; //Array of previous positions, used for the recursive backtracker algorithm

    for(var r = 0; r < colorSteps; r++)
    {
        for(var g = 0; g < colorSteps; g++)
        {
            for(var b = 0; b < colorSteps; b++)
            {
                colors.push(new Color(r * 255 / (colorSteps - 1), g * 255 / (colorSteps - 1), b * 255 / (colorSteps - 1)));
                //Fill the array with all colors
            }
        }
    }

    colors.sort(function(a,b)
    {
        if (a.r < b.r)
            return -1;
        if (a.r > b.r)
            return 1;
        if (a.g < b.g)
            return -1;
        if (a.g > b.g)
            return 1;
        if (a.b < b.b)
            return -1;
        if (a.b > b.b)
            return 1;
        return 0;
    });

    for(var x = 0; x < width; x++)
    {
        grid.push(new Array());
        for(var y = 0; y < height; y++)
        {
            grid[x].push(0); //Set up the grid
            //ChangePixel(imageData, x, y, colors[x + (y * width)]);
        }
    }

    currentPos = new Point(Math.floor(Math.random() * width),Math.floor(Math.random() * height)); 

    grid[currentPos.x][currentPos.y] = 1;
    prevPositions.push(currentPos);
    ChangePixel(imageData, currentPos.x, currentPos.y, colors.pop());

    if(bInstantDraw)
    {
        do
        {
            var notMoved = true;
            while(notMoved)
            {
                var availableSpaces = CheckForSpaces(grid);

                if(availableSpaces.length > 0)
                {
                    var test = availableSpaces[Math.floor(Math.random() * availableSpaces.length)];
                    prevPositions.push(currentPos);
                    currentPos = test;
                    grid[currentPos.x][currentPos.y] = 1;
                    ChangePixel(imageData, currentPos.x, currentPos.y, colors.pop());
                    notMoved = false;
                }
                else
                {
                    if(prevPositions.length != 0)
                    {
                        currentPos = prevPositions.pop();
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
        while(prevPositions.length > 0)

        ctx.putImageData(imageData,0,0);
    }
    else
    {
        setInterval(GameLoop, 1000 / FPS);
    }
}

// Main program loop
function GameLoop()
{
    Update();
    Draw();
}

// Game logic goes here
function Update()
{
    if(!bDone)
    {
        var counter = MOVES_PER_UPDATE;
        while(counter > 0) //For speeding up the drawing
        {
            var notMoved = true;
            while(notMoved)
            {
                var availableSpaces = CheckForSpaces(grid); //Find available spaces

                if(availableSpaces.length > 0) //If there are available spaces
                {
                    prevPositions.push(currentPos); //add old position to prevPosition array
                    currentPos = availableSpaces[Math.floor(Math.random() * availableSpaces.length)]; //pick a random available space
                    grid[currentPos.x][currentPos.y] = 1; //set that space to filled
                    ChangePixel(imageData, currentPos.x, currentPos.y, colors.pop()); //pop color of the array and put it in that space
                    notMoved = false;
                }
                else
                {
                    if(prevPositions.length != 0)
                    {
                        currentPos = prevPositions.pop(); //pop to previous position where spaces are available
                    }
                    else
                    {
                        bDone = true;
                        break;
                    }
                }
            }
            counter--;
        }
    }
}
function Draw()
{
    // Clear the screen
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    ctx.fillStyle='#000000';
    ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    ctx.putImageData(imageData,0,0);
}

function CheckForSpaces(inGrid) //Checks for available spaces then returns back all available spaces
{
    var availableSpaces = [];

    if(currentPos.x > 0 && inGrid[currentPos.x - 1][currentPos.y] == 0)
    {
        availableSpaces.push(new Point(currentPos.x - 1, currentPos.y));
    }

    if(currentPos.x < width - 1 && inGrid[currentPos.x + 1][currentPos.y] == 0)
    {
        availableSpaces.push(new Point(currentPos.x + 1, currentPos.y));
    }

    if(currentPos.y > 0 && inGrid[currentPos.x][currentPos.y - 1] == 0)
    {
        availableSpaces.push(new Point(currentPos.x, currentPos.y - 1));
    }

    if(currentPos.y < height - 1 && inGrid[currentPos.x][currentPos.y + 1] == 0)
    {
        availableSpaces.push(new Point(currentPos.x, currentPos.y + 1));
    }

    return availableSpaces;
}

function ChangePixel(data, x, y, color) //Quick function to simplify changing pixels
{
    data.data[((x + (y * width)) * 4) + 0] = color.r;
    data.data[((x + (y * width)) * 4) + 1] = color.g;
    data.data[((x + (y * width)) * 4) + 2] = color.b;
    data.data[((x + (y * width)) * 4) + 3] = 255;
}

/*Needed Classes*/
function Point(xIn, yIn)
{
    this.x = xIn;
    this.y = yIn;
}

function Color(r, g, b)
{
    this.r = r;
    this.g = g;
    this.b = b;
    this.hue = Math.atan2(Math.sqrt(3) * (this.g - this.b), 2 * this.r - this.g, this.b);
    this.min = Math.min(this.r, this.g);
    this.min = Math.min(this.min, this.b);
    this.min /= 255;
    this.max = Math.max(this.r, this.g);
    this.max = Math.max(this.max, this.b);
    this.max /= 255;
    this.luminance = (this.min + this.max) / 2;
    if(this.min === this.max)
    {
        this.saturation = 0;
    }
    else if(this.luminance < 0.5)
    {
        this.saturation = (this.max - this.min) / (this.max + this.min);
    }
    else if(this.luminance >= 0.5)
    {
        this.saturation = (this.max - this.min) / (2 - this.max - this.min);
    }
}

Картинка 256х128, цвета отсортированы по красному-> зеленому-> синему
RGB сортированные цвета

Картинка 256х128, цвета отсортированы по синим-> зеленым-> красным
BGR сортированные цвета

Картинка 256x128, цвета отсортированы по оттенку-> яркость-> насыщенность
HLS сортированные цвета

И, наконец, GIF одного генерируемого
Цветной Лабиринт GIF


Ваши цвета обрезаются в самых ярких областях, вызывая дублирование. Изменение r * Math.ceil(255 / (colorSteps - 1)к r * Math.floor(255 / (colorSteps - 1), или даже лучше: r * 255 / (colorSteps - 1)(тестировался, так как вы не поставить jsfiddle)
Марк Иероним

Ой, да, у меня было чувство, что это вызовет проблемы, надеюсь, это сейчас исправлено, и извините за отсутствие jsfiddle (я не знал, что это было!) Спасибо!
Kuligoawesome

Мне нравится упорядоченный вывод, похожий на хаос / шум, этого и другого решения, которое дает аналогичные результаты.
r_alex_hall

17

C #

Поэтому я начал работать над этим, как забавное упражнение, и в результате получил результат, который, по крайней мере, для меня выглядит довольно аккуратно. Основное отличие в моем решении (по крайней мере) от большинства других заключается в том, что я генерирую ровно столько цветов, сколько нужно для начала, и равномерно распределяю поколение от чистого белого до чистого черного. Я также устанавливаю цвета, работая по внутренней спирали, и выбираю следующий цвет на основе среднего значения разницы в цвете между всеми установленными соседями.

Вот небольшой пример вывода, который я произвел до сих пор, я работаю над 4k рендером, но я ожидаю, что для его завершения потребуется более одного дня.

Вот пример вывода спецификации в 256x128:

Spec Render

Некоторые большие изображения с разумным временем рендеринга:

Рендеринг с разрешением 360 x 240

Второй запуск при разрешении 360 x 240 обеспечивает более плавное изображение

Render # 2 в 360 x 240

После повышения производительности я смог запустить HD-рендеринг, который занял 2 дня, я еще не отказался от 4K, но это может занять недели.

HD Render

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;

namespace SandBox
{
    class Program
    {
        private static readonly List<Point> directions = new List<Point>
        {
            new Point(1, 0),
            new Point(0, 1),
            new Point(-1, 0),
            new Point(0, -1)
        };

        static void Main(string[] args)
        {
            if (args.Length != 2)
            {
                HelpFile();
                return;
            }
            try
            {
                var config = new ColorGeneratorConfig
                {
                    XLength = int.Parse(args[0]),
                    YLength = int.Parse(args[1])
                };

                Console.WriteLine("Starting image generation with:");
                Console.WriteLine($"\tDimensions:\t\t{config.XLength} X {config.YLength}");
                Console.WriteLine($"\tSteps Per Channel:\t{config.NumOfSteps}");
                Console.WriteLine($"\tStep Size:\t\t{config.ColorStep}");
                Console.WriteLine($"\tSteps to Skip:\t\t{config.StepsToSkip}\n");

                var runner = new TaskRunner();
                var colors = runner.Run(() => GenerateColorList(config), "color selection");
                var pixels = runner.Run(() => BuildPixelArray(colors, config), "pixel array generation");
                runner.Run(() => OutputBitmap(pixels, config), "bitmap creation");
            }
            catch (Exception ex)
            {
               HelpFile("There was an issue in execution");
            }

            Console.ReadLine();
        }

        private static void HelpFile(string errorMessage = "")
        {
            const string Header = "Generates an image with every pixel having a unique color";
            Console.WriteLine(errorMessage == string.Empty ? Header : $"An error has occured: {errorMessage}\n Ensure the Arguments you have provided are valid");
            Console.WriteLine();
            Console.WriteLine($"{AppDomain.CurrentDomain.FriendlyName} X Y");
            Console.WriteLine();
            Console.WriteLine("X\t\tThe Length of the X dimension eg: 256");
            Console.WriteLine("Y\t\tThe Length of the Y dimension eg: 128");
        }

        public static List<Color> GenerateColorList(ColorGeneratorConfig config)
        {

            //Every iteration of our color generation loop will add the iterationfactor to this accumlator which is used to know when to skip
            decimal iterationAccumulator = 0;

            var colors = new List<Color>();
            for (var r = 0; r < config.NumOfSteps; r++)
                for (var g = 0; g < config.NumOfSteps; g++)
                    for (var b = 0; b < config.NumOfSteps; b++)
                    {
                        iterationAccumulator += config.IterationFactor;

                        //If our accumulator has reached 1, then subtract one and skip this iteration
                        if (iterationAccumulator > 1)
                        {
                            iterationAccumulator -= 1;
                            continue;
                        }

                        colors.Add(Color.FromArgb(r*config.ColorStep, g*config.ColorStep,b*config.ColorStep));
                    }
            return colors;
        }

        public static Color?[,] BuildPixelArray(List<Color> colors, ColorGeneratorConfig config)
        {
            //Get a random color to start with.
            var random = new Random(Guid.NewGuid().GetHashCode());
            var nextColor = colors[random.Next(colors.Count)];

            var pixels = new Color?[config.XLength, config.YLength];
            var currPixel = new Point(0, 0);

            var i = 0;

            //Since we've only generated exactly enough colors to fill our image we can remove them from the list as we add them to our image and stop when none are left.
            while (colors.Count > 0)
            {
                i++;

                //Set the current pixel and remove the color from the list.
                pixels[currPixel.X, currPixel.Y] = nextColor;
                colors.RemoveAt(colors.IndexOf(nextColor));

                //Our image generation works in an inward spiral generation GetNext point will retrieve the next pixel given the current top direction.
                var nextPixel = GetNextPoint(currPixel, directions.First());

                //If this next pixel were to be out of bounds (for first circle of spiral) or hit a previously generated pixel (for all other circles)
                //Then we need to cycle the direction and get a new next pixel
                if (nextPixel.X >= config.XLength || nextPixel.Y >= config.YLength || nextPixel.X < 0 || nextPixel.Y < 0 ||
                    pixels[nextPixel.X, nextPixel.Y] != null)
                {
                    var d = directions.First();
                    directions.RemoveAt(0);
                    directions.Add(d);
                    nextPixel = GetNextPoint(currPixel, directions.First());
                }

                //This code sets the pixel to pick a color for and also gets the next color
                //We do this at the end of the loop so that we can also support haveing the first pixel set outside of the loop
                currPixel = nextPixel;

                if (colors.Count == 0) continue;

                var neighbours = GetNeighbours(currPixel, pixels, config);
                nextColor = colors.AsParallel().Aggregate((item1, item2) => GetAvgColorDiff(item1, neighbours) <
                                                                            GetAvgColorDiff(item2, neighbours)
                    ? item1
                    : item2);
            }

            return pixels;
        }

        public static void OutputBitmap(Color?[,] pixels, ColorGeneratorConfig config)
        {
            //Now that we have generated our image in the color array we need to copy it into a bitmap and save it to file.
            var image = new Bitmap(config.XLength, config.YLength);

            for (var x = 0; x < config.XLength; x++)
                for (var y = 0; y < config.YLength; y++)
                    image.SetPixel(x, y, pixels[x, y].Value);

            using (var file = new FileStream($@".\{config.XLength}X{config.YLength}.png", FileMode.Create))
            {
                image.Save(file, ImageFormat.Png);
            }
        }

        static Point GetNextPoint(Point current, Point direction)
        {
            return new Point(current.X + direction.X, current.Y + direction.Y);
        }

        static List<Color> GetNeighbours(Point current, Color?[,] grid, ColorGeneratorConfig config)
        {
            var list = new List<Color>();
            foreach (var direction in directions)
            {
                var xCoord = current.X + direction.X;
                var yCoord = current.Y + direction.Y;
                if (xCoord < 0 || xCoord >= config.XLength|| yCoord < 0 || yCoord >= config.YLength)
                {
                    continue;
                }
                var cell = grid[xCoord, yCoord];
                if (cell.HasValue) list.Add(cell.Value);
            }
            return list;
        }

        static double GetAvgColorDiff(Color source, IList<Color> colors)
        {
            return colors.Average(color => GetColorDiff(source, color));
        }

        static int GetColorDiff(Color color1, Color color2)
        {
            var redDiff = Math.Abs(color1.R - color2.R);
            var greenDiff = Math.Abs(color1.G - color2.G);
            var blueDiff = Math.Abs(color1.B - color2.B);
            return redDiff + greenDiff + blueDiff;
        }
    }

    public class ColorGeneratorConfig
    {
        public int XLength { get; set; }
        public int YLength { get; set; }

        //Get the number of permutations for each color value base on the required number of pixels.
        public int NumOfSteps
            => (int)Math.Ceiling(Math.Pow((ulong)XLength * (ulong)YLength, 1.0 / ColorDimensions));

        //Calculate the increment for each step
        public int ColorStep
            => 255 / (NumOfSteps - 1);

        //Because NumOfSteps will either give the exact number of colors or more (never less) we will sometimes to to skip some
        //this calculation tells how many we need to skip
        public decimal StepsToSkip
            => Convert.ToDecimal(Math.Pow(NumOfSteps, ColorDimensions) - XLength * YLength);

        //This factor will be used to as evenly as possible spread out the colors to be skipped so there are no large gaps in the spectrum
        public decimal IterationFactor => StepsToSkip / Convert.ToDecimal(Math.Pow(NumOfSteps, ColorDimensions));

        private double ColorDimensions => 3.0;
    }

    public class TaskRunner
    {
        private Stopwatch _sw;
        public TaskRunner()
        {
            _sw = new Stopwatch();
        }

        public void Run(Action task, string taskName)
        {
            Console.WriteLine($"Starting {taskName}...");
            _sw.Start();
            task();
            _sw.Stop();
            Console.WriteLine($"Finished {taskName}. Elapsed(ms): {_sw.ElapsedMilliseconds}");
            Console.WriteLine();
            _sw.Reset();
        }

        public T Run<T>(Func<T> task, string taskName)
        {
            Console.WriteLine($"Starting {taskName}...");
            _sw.Start();
            var result = task();
            _sw.Stop();
            Console.WriteLine($"Finished {taskName}. Elapsed(ms): {_sw.ElapsedMilliseconds}");
            Console.WriteLine();
            _sw.Reset();
            return result;
        }
    }
}

Если у кого-то есть какие-либо мысли о том, как улучшить производительность алгоритма выбора цвета, пожалуйста, дайте мне знать, так как он стоит 360 * 240, рендеринг занимает около 15 минут. Я не верю, что это может быть распараллелено, но мне интересно, будет ли более быстрый способ получить самый низкий цветовой контраст.
Phaeze

Как изображение 360 * 240 составляет «все цвета»? Как вы производите cbrt (360 * 240) = 44.208377983684639269357874002958 цветов на компонент?
Марк Иеронимус

Какой это язык? Рандомизация сортировки списка и рандома - плохая идея, потому что, в зависимости от алгоритма и реализации, это может привести к искаженному результату или исключению, утверждающему, что "Comparison method violates its general contract!": потому что в контракте это указано (x.compareTo(y)>0 && y.compareTo(z)>0) implies x.compareTo(z)>0. Чтобы рандомизировать список, используйте предоставленный метод Shuffle. ( colors.Shuffle()?)
Марк Иеронимус

@MarkJeronimus Я признаю, что пропустил спецификацию об изображении 256x128, я сделаю простые рендеры с использованием этих размеров, я сосредоточился на том, чтобы каждый пиксель представлял собой уникальный цветовой аспект задачи и большие рендеры, как и другие материалы.
Phaeze

@MarkJeronimus Я понимаю, что случайная сортировка плохая, на самом деле есть комментарий, который говорит об этом. Это был всего лишь остаток другого подхода, который я начал применять, и я отдавал приоритет выполнению крупных рендеров, так как они занимают очень много времени.
Phaeze

16

Идти

Вот еще один от меня, я думаю, что он дает более интересные результаты:

package main

import (
    "image"
    "image/color"
    "image/png"
    "os"

    "math"
    "math/rand"
)

func distance(c1, c2 color.Color) float64 {
    r1, g1, b1, _ := c1.RGBA()
    r2, g2, b2, _ := c2.RGBA()
    rd, gd, bd := int(r1)-int(r2), int(g1)-int(g2), int(b1)-int(b2)
    return math.Sqrt(float64(rd*rd + gd*gd + bd*bd))
}

func main() {
    allcolor := image.NewRGBA(image.Rect(0, 0, 256, 128))
    for y := 0; y < 128; y++ {
        for x := 0; x < 256; x++ {
            allcolor.Set(x, y, color.RGBA{uint8(x%32) * 8, uint8(y%32) * 8, uint8(x/32+(y/32*8)) * 8, 255})
        }
    }

    for y := 0; y < 128; y++ {
        for x := 0; x < 256; x++ {
            rx, ry := rand.Intn(256), rand.Intn(128)

            c1, c2 := allcolor.At(x, y), allcolor.At(rx, ry)
            allcolor.Set(x, y, c2)
            allcolor.Set(rx, ry, c1)
        }
    }

    for i := 0; i < 16384; i++ {
        for y := 0; y < 128; y++ {
            for x := 0; x < 256; x++ {
                xl, xr := (x+255)%256, (x+1)%256
                cl, c, cr := allcolor.At(xl, y), allcolor.At(x, y), allcolor.At(xr, y)
                dl, dr := distance(cl, c), distance(c, cr)
                if dl < dr {
                    allcolor.Set(xl, y, c)
                    allcolor.Set(x, y, cl)
                }

                yu, yd := (y+127)%128, (y+1)%128
                cu, c, cd := allcolor.At(x, yu), allcolor.At(x, y), allcolor.At(x, yd)
                du, dd := distance(cu, c), distance(c, cd)
                if du < dd {
                    allcolor.Set(x, yu, c)
                    allcolor.Set(x, y, cu)
                }
            }
        }
    }

    filep, err := os.Create("EveryColor.png")
    if err != nil {
        panic(err)
    }
    err = png.Encode(filep, allcolor)
    if err != nil {
        panic(err)
    }
    filep.Close()
}

Он начинается с того же шаблона, что и GIF в моем другом ответе . Затем это перемешивает это в это:

просто шум

Чем больше итераций я запускаю довольно скучный алгоритм сравнения соседей, тем более очевидным становится радуга.

Вот 16384:

очень шумная радуга на 16384 итерации

И 65536:

введите описание изображения здесь


6
+1 Мне нравится, что из этого вытекает закономерность; Вы должны сделать анимацию этого!
Джейсон C

16

Эти изображения "Радуга Лэнгтона". Они нарисованы довольно просто: когда муравей Лэнгтона движется, цвет рисуется на каждом пикселе при первом посещении пикселя. Затем цвет для рисования просто увеличивается на 1, обеспечивая использование 2 ^ 15 цветов, по одному на каждый пиксель.

РЕДАКТИРОВАТЬ: я сделал версию, которая отображает 4096X4096 изображений, используя 2 ^ 24 цветов. Цвета также «отражаются», поэтому они создают плавные градиенты. Я буду давать только ссылки на них, так как они огромные (> 28 МБ)

Радуга Лэнгтона большая, правило LR

Радуга Лэнгтона большая, правило LLRR

// Конец редактирования.

Это для классического набора правил LR:

Langton's Rainbow LR

Вот LLRR:

Радуга Лэнгтона LLRR

Наконец, этот использует набор правил LRRRRRLLR:

Радуга Лэнгтона LRRRRRLLR

Написан на C ++ с использованием CImg для графики. Я должен также упомянуть, как были выбраны цвета: во-первых, я использую шрифт без знака, чтобы содержать данные о цвете RGB. Каждый раз, когда ячейка посещается впервые, я сдвигаю биты вправо на несколько, кратное 5, И на 31, затем умножаю на 8. Затем краткий цвет без знака увеличивается на 1. Это дает значения от 0 до 248 максимум. Однако я вычел это значение из 255 в красной и синей компонентах, поэтому R и B кратны 8, начиная с 255 до 7:

c[0]=255-((color&0x1F)*8);
c[2]=255-(((color>>5)&0x1F)*8);
c[1]=(((color>>10)&0x1F)*8);

Однако это не относится к зеленому компоненту, который кратен 8 от 0 до 248. В любом случае каждый пиксель должен содержать уникальный цвет.

В любом случае, исходный код ниже:

#include "CImg.h"
using namespace cimg_library;
CImgDisplay screen;
CImg<unsigned char> surf;
#define WIDTH 256
#define HEIGHT 128
#define TOTAL WIDTH*HEIGHT
char board[WIDTH][HEIGHT];


class ant
{
  public:
  int x,y;
  char d;
  unsigned short color;
  void init(int X, int Y,char D)
  {
    x=X;y=Y;d=D;
    color=0;
  }

  void turn()
  {
    ///Have to hard code for the rule set here.
    ///Make sure to set RULECOUNT to the number of rules!
    #define RULECOUNT 9
    //LRRRRRLLR
    char get=board[x][y];
    if(get==0||get==6||get==7){d+=1;}
    else{d-=1;}
    if(d<0){d=3;}
    else if(d>3){d=0;}
  }

  void forward()
  {
    if(d==0){x++;}
    else if(d==1){y--;}
    else if(d==2){x--;}
    else {y++;}
    if(x<0){x=WIDTH-1;}
    else if(x>=WIDTH){x=0;}
    if(y<0){y=HEIGHT-1;}
    else if(y>=HEIGHT){y=0;}
  }

  void draw()
  {
    if(board[x][y]==-1)
    {
      board[x][y]=0;
      unsigned char c[3];
      c[0]=255-((color&0x1F)*8);
      c[2]=255-(((color>>5)&0x1F)*8);
      c[1]=(((color>>10)&0x1F)*8);
      surf.draw_point(x,y,c);
      color++;
    }

    board[x][y]++;
    if(board[x][y]==RULECOUNT){board[x][y]=0;}

  }

  void step()
  {
    draw();
    turn();
    forward();
  }
};

void renderboard()
{
  unsigned char white[]={200,190,180};
  surf.draw_rectangle(0,0,WIDTH,HEIGHT,white);
  for(int x=0;x<WIDTH;x++)
  for(int y=0;y<HEIGHT;y++)
  {
    char get=board[x][y];
    if(get==1){get=1;unsigned char c[]={255*get,255*get,255*get};
    surf.draw_point(x,y,c);}
    else if(get==0){get=0;unsigned char c[]={255*get,255*get,255*get};
    surf.draw_point(x,y,c);}
  }
}

int main(int argc, char** argv)
{

  screen.assign(WIDTH*3,HEIGHT*3);
  surf.assign(WIDTH,HEIGHT,1,3);
  ant a;
  a.init(WIDTH/2,HEIGHT/2,2);
  surf.fill(0);
  for(int x=0;x<WIDTH;x++)
  for(int y=0;y<HEIGHT;y++)
  {
    board[x][y]=-1;
  }

  while(a.color<TOTAL)
  {
    a.step();
  }

  screen=surf;
  while(screen.is_closed()==false)
  {
    screen.wait();
  }
  surf.save_bmp("LangtonsRainbow.bmp");
  return 0;
}

1
Добро пожаловать и вступайте в клуб. Может быть, интересно попробовать другие турмиты из code.google.com/p/ruletablerepository/wiki/… (я участвовал в этом)
Марк Джеронимус

3
Ссылки на изображения устарели, потому что Dropbox убил публичные папки.
user2428118

15

Рубин

Я решил, что буду делать PNG с нуля, потому что думал, что это будет интересно. Этот код буквально выводит сырой двоичные данные в файл.

Я сделал версию 512x512. (Хотя алгоритм довольно неинтересен.) На моем компьютере он заканчивается примерно за 3 секунды.

require 'zlib'

class RBPNG
  def initialize
    # PNG header
    @data = [137, 80, 78, 71, 13, 10, 26, 10].pack 'C*'
  end

  def chunk name, data = ''
    @data += [data.length].pack 'N'
    @data += name
    @data += data
    @data += [Zlib::crc32(name + data)].pack 'N'
  end

  def IHDR opts = {}
    opts = {bit_depth: 8, color_type: 6, compression: 0, filter: 0, interlace: 0}.merge opts
    raise 'IHDR - Missing width param' if !opts[:width]
    raise 'IHDR - Missing height param' if !opts[:height]

    self.chunk 'IHDR', %w[width height].map {|x| [opts[x.to_sym]].pack 'N'}.join +
                       %w[bit_depth color_type compression filter interlace].map {|x| [opts[x.to_sym]].pack 'C'}.join
  end

  def IDAT data; self.chunk 'IDAT', Zlib.deflate(data); end
  def IEND; self.chunk 'IEND'; end
  def write filename; IO.binwrite filename, @data; end
end

class Color
  attr_accessor :r, :g, :b, :a

  def initialize r = 0, g = 0, b = 0, a = 255
    if r.is_a? Array
      @r, @g, @b, @a = @r
      @a = 255 if !@a
    else
      @r = r
      @g = g
      @b = b
      @a = a
    end
  end

  def hex; '%02X' * 4 % [@r, @g, @b, @a]; end
  def rgbhex; '%02X' * 3 % [@r, @g, @b]; end
end

img = RBPNG.new
img.IHDR({width: 512, height: 512, color_type: 2})
#img.IDAT ['00000000FFFFFF00FFFFFF000000'].pack 'H*'
r = g = b = 0
data = Array.new(512){ Array.new(512){
  c = Color.new r, g, b
  r += 4
  if r == 256
    r = 0
    g += 4
    if g == 256
      g = 0
      b += 4
    end
  end
  c
} }
img.IDAT [data.map {|x| '00' + x.map(&:rgbhex).join }.join].pack 'H*'
img.IEND
img.write 'all_colors.png'

Вывод (в all_colors.png) (нажмите на любое из этих изображений, чтобы увеличить их):

Выход

Несколько более интересный вывод градиента (путем изменения 4-й строки в последнюю }.shuffle }):

Выход 2

И изменив его на }.shuffle }.shuffle, вы получите сумасшедшие цветные линии:

Выход 3


Это действительно круто. Есть ли способ сделать его более красивым? Может быть, рандомизировать пиксели? Scoring is by vote. Vote for the most beautiful images made by the most elegant code.

1
@LowerClassOverflowian Хорошо, отредактировано
ручка двери

Намного лучше!!!!!!!

1
Что произойдет, если вы изменили 4-ую строку на последнюю }.shuffle }.shuffle }.shuffle?
Джон Одом

6
@ Джон Эмм, синтаксическая ошибка, наверное?
Ручка двери

14

питон

плазма

Использование python для сортировки цветов по яркости, генерация шаблона яркости и выбор наиболее подходящего цвета. Пиксели итерируются в случайном порядке, так что менее подходящие совпадения яркости, которые естественным образом случаются, когда список доступных цветов уменьшается, равномерно распределяются по всему изображению.

#!/usr/bin/env python

from PIL import Image
from math import pi, sin, cos
import random

WIDTH = 256
HEIGHT = 128

img = Image.new("RGB", (WIDTH, HEIGHT))

colors = [(x >> 10, (x >> 5) & 31, x & 31) for x in range(32768)]
colors = [(x[0] << 3, x[1] << 3, x[2] << 3) for x in colors]
colors.sort(key=lambda x: x[0] * 0.2126 + x[1] * 0.7152 + x[2] * 0.0722)

def get_pixel(lum):
    for i in range(len(colors)):
        c = colors[i]
        if c[0] * 0.2126 + c[1] * 0.7152 + c[2] * 0.0722 > lum:
            break
    return colors.pop(i)

def plasma(x, y):
    x -= WIDTH / 2
    p = sin(pi * x / (32 + 10 * sin(y * pi / 32)))
    p *= cos(pi * y / 64)
    return 128 + 127 * p

xy = []
for x in range(WIDTH):
    for y in range(HEIGHT):
        xy.append((x, y))
random.shuffle(xy)

count = 0
for x, y in xy:
    l = int(plasma(x, y))
    img.putpixel((x, y), get_pixel(plasma(x, y)))
    count += 1
    if not count & 255:
        print "%d pixels rendered" % count

img.save("test.png")

13

Ява

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
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);
        int num = 0;
        ArrayList<Point> points = new ArrayList<>();
        for (int y = 0; y < 4096; y++) {
            for (int x = 0; x < 4096 ; x++) {
                points.add(new Point(x, y));
            }
        }
        for (Point p : points) {
            int x = p.x;
            int y = p.y;

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

Я пошел на 4096 к 4096, потому что я не мог понять, как получить все цвета без этого.

Выход:

Слишком большой, чтобы поместиться здесь. Это скриншот:

введите описание изображения здесь

С небольшими изменениями мы можем получить более красивую картинку:

Добавьте Collections.shuffle(points, new Random(0));между созданием точек и выполнением цветов:

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
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);
        int num = 0;
        ArrayList<Point> points = new ArrayList<>();
        for (int y = 0; y < 4096; y++) {
            for (int x = 0; x < 4096 ; x++) {
                points.add(new Point(x, y));
            }
        }
        Collections.shuffle(points, new Random(0));
        for (Point p : points) {
            int x = p.x;
            int y = p.y;

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

введите описание изображения здесь

Крупный план:

введите описание изображения здесь


29
Вы называете большой серый шарик "красивым"?
Ручка двери

22
@ Doorknob Да. Я называю это очень красиво. Я нахожу удивительным, что все цвета могут быть объединены в большой серый шарик. Я нахожу этот BLOB-объект более интересным, когда увеличиваю масштаб. С немного большей детализацией вы можете увидеть, как работает неслучайный Java. Когда мы увеличиваем еще больше, как на втором снимке экрана, становится ясно, сколько цветов в этой вещи. Когда я увеличиваю масштаб изображения, это выглядит как программа Piet.
Джастин

Я получил цвета в меньших версиях, опустив нижние биты.
Марк Жеронимус

Да, младшие биты для r, gи по bотдельности, но я имел дело с ними , как один номер.
Джастин

Я вижу, вы выяснили, что Мэджик в вашем следующем ответе. По теме, может быть интересно поэкспериментировать с вашим собственным Randomподклассом, который производит еще менее идеальные случайные числа.
Марк Жеронимус

13

C ++ 11

( Обновление: только потом я заметил, что подобный подход уже был опробован - с большим терпением в отношении количества итераций.)

Для каждого пикселя я определяю набор соседних пикселей. Я определяю расхождение между двумя пикселями как сумму квадратов их разностей R / G / B. Наказанием данного пикселя является сумма расхождений между пикселем и его соседями.

Теперь я сначала генерирую случайную перестановку, затем начинаю выбирать случайные пары пикселей. Если замена двух пикселей уменьшает сумму общих штрафов всех пикселей, обмен выполняется. Я повторяю это миллион раз.

Вывод в формате PPM, который я преобразовал в PNG, используя стандартные утилиты.

Источник:

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <random>

static std::mt19937 rng;

class Pixel
{
public:
    int r, g, b;

    Pixel() : r(0), g(0), b(0) {}
    Pixel(int r, int g, int b) : r(r), g(g), b(b) {}

    void swap(Pixel& p)
    {
        int r = this->r,  g = this->g,    b = this->b;
        this->r = p.r;    this->g = p.g;  this->b = p.b;
        p.r = r;          p.g = g;        p.b = b;
    }
};

class Image
{
public:
    static const int width = 256;
    static const int height = 128;
    static const int step = 32;
    Pixel pixel[width*height];
    int penalty[width*height];
    std::vector<int>** neighbors;

    Image()
    {
        if (step*step*step != width*height)
        {
            std::cerr << "parameter mismatch" << std::endl;
            exit(EXIT_FAILURE);
        }

        neighbors = new std::vector<int>*[width*height];

        for (int i = 0; i < width*height; i++)
        {
            penalty[i] = -1;
            neighbors[i] = pixelNeighbors(i);
        }

        int i = 0;
        for (int r = 0; r < step; r++)
        for (int g = 0; g < step; g++)
        for (int b = 0; b < step; b++)
        {
            pixel[i].r = r * 255 / (step-1);
            pixel[i].g = g * 255 / (step-1);
            pixel[i].b = b * 255 / (step-1);
            i++;
        }
    }

    ~Image()
    {
        for (int i = 0; i < width*height; i++)
        {
            delete neighbors[i];
        }
        delete [] neighbors;
    }

    std::vector<int>* pixelNeighbors(const int pi)
    {
        // 01: X-shaped structure
        //const int iRad = 7, jRad = 7;
        //auto condition = [](int i, int j) { return abs(i) == abs(j); };
        //
        // 02: boring blobs
        //const int iRad = 7, jRad = 7;
        //auto condition = [](int i, int j) { return true; };
        //
        // 03: cross-shaped
        //const int iRad = 7, jRad = 7;
        //auto condition = [](int i, int j) { return i==0 || j == 0; };
        //
        // 04: stripes
        const int iRad = 1, jRad = 5;
        auto condition = [](int i, int j) { return i==0 || j == 0; };

        std::vector<int>* v = new std::vector<int>;

        int x = pi % width;
        int y = pi / width;

        for (int i = -iRad; i <= iRad; i++)
        for (int j = -jRad; j <= jRad; j++)
        {
            if (!condition(i,j))
                continue;

            int xx = x + i;
            int yy = y + j;

            if (xx < 0 || xx >= width || yy < 0 || yy >= height)
                continue;

            v->push_back(xx + yy*width);
        }

        return v;
    }

    void shuffle()
    {
        for (int i = 0; i < width*height; i++)
        {
            std::uniform_int_distribution<int> dist(i, width*height - 1);
            int j = dist(rng);
            pixel[i].swap(pixel[j]);
        }
    }

    void writePPM(const char* filename)
    {
        std::ofstream fd;
        fd.open(filename);
        if (!fd.is_open())
        {
            std::cerr << "failed to open file " << filename
                      << "for writing" << std::endl;
            exit(EXIT_FAILURE);
        }
        fd << "P3\n" << width << " " << height << "\n255\n";
        for (int i = 0; i < width*height; i++)
        {
            fd << pixel[i].r << " " << pixel[i].g << " " << pixel[i].b << "\n";
        }
        fd.close();
    }

    void updatePixelNeighborhoodPenalty(const int pi)
    {
        for (auto j : *neighbors[pi])
            updatePixelPenalty(j);
    }

    void updatePixelPenalty(const int pi)
    {
        auto pow2 = [](int x) { return x*x; };
        int pen = 0;
        Pixel* p1 = &pixel[pi];
        for (auto j : *neighbors[pi])
        {
            Pixel* p2 = &pixel[j];
            pen += pow2(p1->r - p2->r) + pow2(p1->g - p2->g) + pow2(p1->b - p2->b);
        }
        penalty[pi] = pen / neighbors[pi]->size();
    }

    int getPixelPenalty(const int pi)
    {
        if (penalty[pi] == (-1))
        {
            updatePixelPenalty(pi);
        }
        return penalty[pi];
    }

    int getPixelNeighborhoodPenalty(const int pi)
    {
        int sum = 0;
        for (auto j : *neighbors[pi])
        {
            sum += getPixelPenalty(j);
        }
        return sum;
    }

    void iterate()
    {
        std::uniform_int_distribution<int> dist(0, width*height - 1);       

        int i = dist(rng);
        int j = dist(rng);

        int sumBefore = getPixelNeighborhoodPenalty(i)
                        + getPixelNeighborhoodPenalty(j);

        int oldPenalty[width*height];
        std::copy(std::begin(penalty), std::end(penalty), std::begin(oldPenalty));

        pixel[i].swap(pixel[j]);
        updatePixelNeighborhoodPenalty(i);
        updatePixelNeighborhoodPenalty(j);

        int sumAfter = getPixelNeighborhoodPenalty(i)
                       + getPixelNeighborhoodPenalty(j);

        if (sumAfter > sumBefore)
        {
            // undo the change
            pixel[i].swap(pixel[j]);
            std::copy(std::begin(oldPenalty), std::end(oldPenalty), std::begin(penalty));
        }
    }
};

int main(int argc, char* argv[])
{
    int seed;
    if (argc >= 2)
    {
        seed = atoi(argv[1]);
    }
    else
    {
        std::random_device rd;
        seed = rd();
    }
    std::cout << "seed = " << seed << std::endl;
    rng.seed(seed);

    const int numIters = 1000000;
    const int progressUpdIvl = numIters / 100;
    Image img;
    img.shuffle();
    for (int i = 0; i < numIters; i++)
    {
        img.iterate();
        if (i % progressUpdIvl == 0)
        {
            std::cout << "\r" << 100 * i / numIters << "%";
            std::flush(std::cout);
        }
    }
    std::cout << "\rfinished!" << std::endl;
    img.writePPM("AllColors2.ppm");

    return EXIT_SUCCESS;
}

Изменение шага соседей дает разные результаты. Это можно настроить в функции Image :: pixelNeighbors (). Код включает в себя примеры для четырех вариантов: (см. Источник)

пример 01 пример 02 пример 03 пример 04

Изменить: еще один пример, похожий на четвертый выше, но с большим ядром и большим количеством итераций:

пример 05

Еще один: использование

const int iRad = 7, jRad = 7;
auto condition = [](int i, int j) { return (i % 2==0 && j % 2==0); };

и десять миллионов итераций, я получил это:

пример 06


11

Не самый элегантный код, но интересный по двум причинам: вычисление количества цветов из измерений (при условии, что произведение измерений является степенью двойки), и выполнение мелочей в цветовом пространстве:

void Main()
{
    var width = 256;
    var height = 128;
    var colorCount = Math.Log(width*height,2);
    var bitsPerChannel = colorCount / 3;
    var channelValues = Math.Pow(2,bitsPerChannel);
    var channelStep = (int)(256/channelValues);

    var colors = new List<Color>();

    var m1 = new double[,] {{0.6068909,0.1735011,0.2003480},{0.2989164,0.5865990,0.1144845},{0.00,0.0660957,1.1162243}};
    for(var r=0;r<255;r+=channelStep)
    for(var g=0;g<255;g+=channelStep)
    for(var b=0;b<255;b+=channelStep)   
    {
        colors.Add(Color.FromArgb(0,r,g,b));
    }
    var sortedColors = colors.Select((c,i)=>
                            ToLookupTuple(MatrixProduct(m1,new[]{c.R/255d,c.G/255d,c.B/255d}),i))
                            .Select(t=>new
                                            {
                                                x = (t.Item1==0 && t.Item2==0 && t.Item3==0) ? 0 : t.Item1/(t.Item1+t.Item2+t.Item3),
                                                y = (t.Item1==0 && t.Item2==0 && t.Item3==0) ? 0 :t.Item2/(t.Item1+t.Item2+t.Item3),
                                                z = (t.Item1==0 && t.Item2==0 && t.Item3==0) ? 0 :t.Item3/(t.Item1+t.Item2+t.Item3),
                                                Y = t.Item2,
                                                i = t.Item4
                                            })
                            .OrderBy(t=>t.x).Select(t=>t.i).ToList();
    if(sortedColors.Count != (width*height))
    {
        throw new Exception(string.Format("Some colors fell on the floor: {0}/{1}",sortedColors.Count,(width*height)));
    }
    using(var bmp = new Bitmap(width,height,PixelFormat.Format24bppRgb))
    {
        for(var i=0;i<colors.Count;i++)
        {
            var y = i % height;
            var x = i / height;

            bmp.SetPixel(x,y,colors[sortedColors[i]]);
        }
        //bmp.Dump(); //For LINQPad use
        bmp.Save("output.png");
    }
}
static Tuple<double,double,double,int>ToLookupTuple(double[] t, int index)
{
    return new Tuple<double,double,double,int>(t[0],t[1],t[2],index);
}

public static double[] MatrixProduct(double[,] matrixA,
    double[] vectorB)
{
    double[] result=new double[3];
    for (int i=0; i<3; ++i) // each row of A
        for (int k=0; k<3; ++k)
            result[i]+=matrixA[i,k]*vectorB[k];
    return result;
}

Некоторые интересные вариации можно получить, просто изменив предложение OrderBy:

Икс:

введите описание изображения здесь

у:

введите описание изображения здесь

г:

введите описание изображения здесь

Y:

введите описание изображения здесь

Я хотел бы выяснить, что было причиной нечетных строк в первых трех


2
Эти странные строки, вероятно, являются смещением какого-то метода поиска или поиска (бинарный поиск / быстрая сортировка?)
Марк Джеронимус

Мне на самом деле очень нравятся линии здесь.
Джейсон C

11

Ява

Это была намного лучшая идея. Это очень короткий Java-код; основной метод длиной всего 13 строк:

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);
        }
    }
}

Формирует блоки "палитры цветов". В основном, в первом блоке, r=0во втором r=1и т. Д. В каждом блоке gувеличивается по отношению к xи по bотношению кy .

Мне очень нравятся побитовые операторы. Позвольте мне разбить setRGBутверждение:

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

В результате побитовых операторов это занимает всего 7 секунд. Если r & 15заменить на r % 16, это займет 9 секунд.

Я выбрал 4096 х 4096

Вывод (скриншот, слишком большой в противном случае):

введите описание изображения здесь

Вывод со злой усмешкой, нарисованной от руки красными кружками:

введите описание изображения здесь


2
Ссылка на оригинал, чтобы я мог проверить достоверность (количество цветов)
Марк Jeronimus

2
Лол! Я забыл, что могу запустить код Java. Первое изображение проходит, а я не могу воспроизвести второе изображение (смеется) ☺
Марк Джеронимус

16
Все круги от руки имеют одинаковый цвет, дисквалифицированы. : P
Ник Т

3
@Quincunx +1, если вы можете нарисовать страшное лицо и при этом соблюдать требования к цвету!
Джейсон С

2
@JasonC Смотри мой ответ. Кредит идет на Quincunx для вдохновения.
Уровень Ривер Стрит
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.