Зображення з усіма кольорами


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 рази. Я не бачу інших способів її оптимізації, я думаю, що це досить добре, як є.

І велика привабливість, ось перегляд 512х512 пікселів, середній старт, мінімальний вибір:

Я просто не можу перестати грати з цим! У наведеному вище коді кольори сортуються випадковим чином. Якщо ми взагалі не сортуємо або сортуємо за відтінком ( (c1, c2) => c1.GetHue().CompareTo(c2.GetHue())), отримуємо їх відповідно (як середній старт, так і мінімальний вибір):

Ще одна комбінація, де коралова форма зберігається до кінця: відтінок впорядковується із середнім виділенням, з аніггією на 30 кадрів:

ОНОВЛЕННЯ: ГОТОВО !!!

Ви хотіли привіт, я хотів привіт, я був нетерплячий, я ледве спав. Тепер я із задоволенням оголошую, що нарешті готовий, якість виробництва. І я випускаю це з великим ударом, дивовижним 1080p відео YouTube! Клацніть тут для відео , давайте зробимо його віруальним для просування стилю geek. Я також публікую матеріали у своєму блозі на веб-сайті 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 (режим мого другого допису, який я об'єднав у цей): Вибирає випадкову початкову точку на зображенні та проходить шлях шляхом невикористаних пікселів; може гуляти використані пікселі. Параметри цього режиму:
      • Набір напрямків для прогулянки (ортогональна, діагональна або обидві).
      • Чи потрібно змінювати напрямок (в даний час за годинниковою стрілкою, але код є гнучким) після кожного кроку, або змінювати напрям лише після зустрічі із зайнятим пікселем ..
      • Можливість переміщення порядку переміщення (замість годинникової стрілки) змінюється.

Він працює для всіх розмірів до 4096x4096.

Повний ескіз обробки можна знайти тут: Tracer.zip

Я вставив усі файли в один і той же блок коду нижче, щоб заощадити місце (навіть усі в одному файлі, це все-таки дійсна ескіз). Якщо ви хочете скористатися однією з пресетів, змініть індекс у gPresetпризначенні. Якщо це запустити в обробці, ви можете натиснути, rпоки він працює, щоб створити нове зображення.

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

}

Ось повний набір зображень 256х128, які мені подобаються:

Режим 1:

Мій улюблений з оригінального набору (max_path_length = 512, path_step = 2, випадковий, відображається 2х, посилання 256x128 ):

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

Інші (ліві два впорядковані, праворуч два випадкові, два верхніх довжина шляху обмежена, два знизу необмежені):

орліміт randlimit ордноліміт randnolimit

Це може бути викладено плиткою:

ранділ

Режим 2:

алмази квіти boxfade діаговер великі діаманти коробки2 черепки

Їх можна викласти плиткою:

bigtile алмазний подарункова упаковка

512x512 виділень:

Алмази, що зберігаються, мої улюблені з режиму 2; ви можете бачити, як шляхи обходять наявні об'єкти:

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

Більший крок шляху та максимальна довжина шляху, плитка:

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

Випадковий режим 1, розміщений на таблиці:

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

Більше вибору:

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

Усі відображення 512x512 можна знайти в папці "dropbox" (* _64.png).

2048x1024 та 4096x4096:

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

2048x1024 великі кахельні алмази (той самий, з яким я пов’язаний на початку цієї публікації)

2048x1024 алмази (я люблю цей!), Зменшено :

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

4096x4096 великих алмазних плиток (Нарешті! Клацніть «скачати» у посиланні Dropbox; він занадто великий для їх перегляду), зменшений вниз:

4096x4096 великі кахельні алмази

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

4096x4096 ще один класний

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

Є багато хороших, мені дуже важко вибрати, які з них публікувати, тому, будь ласка, перевірте посилання на папку!


6
Це нагадує мені близькі погляди на деякі корисні копалини.
Морвен

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% розміру файлу на цьому зображенні .
користувач2428118

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 кадрів.

Тизер 32x32:

Версію 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
@JasonC Я додав анімацію;)
primo

2
@primo Я знаю, що я спізнююсь, але я просто хотів сказати, що ці зображення є вражаючими.
Ешвін Гупта

130

Я отримав цю ідею з алгоритму користувача fejesjoco і хотів трохи пограти, тому я почав писати власний алгоритм з нуля.

Я публікую це, бо відчуваю, що якщо я можу зробити щось краще *, ніж найкраще з вас, хлопці, я не думаю, що цей виклик ще закінчений. Для порівняння, є кілька приголомшливих конструкцій на allRGB, які я вважаю далеко за рівень, досягнутий тут, і я не знаю, як вони це зробили.

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

Цей алгоритм:

  1. Почніть з (декількох) насінин, кольорами, максимально наближеними до чорного.
  2. Зберігайте список усіх пікселів, які не відвідуються та 8-з’єднані з відвідуваною точкою.
  3. Виберіть випадковий ** пункт із цього списку
  4. Обчисліть середній колір усіх обчислених пікселів [Редагувати ... у квадраті 9х9 за допомогою ядра Гаусса] 8-підключений до нього (це і є причиною, чому він виглядає так гладко) Якщо жодного не знайдено, візьміть чорний.
  5. в кубі 3x3x3 навколо цього кольору, шукайте невикористаний колір.
    • Коли знайдеться кілька кольорів, візьміть найтемніший.
    • Коли знайдено кілька однаково темних кольорів, візьміть випадковий з них.
    • Коли нічого не знайдено, оновіть діапазон пошуку до 5x5x5, 7x7x7 тощо. Повторіть з 5.
  6. Позначте піксель, оновіть список та повторіть з 3

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

Зображення тороїдальне.

Java

Завантажити: 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

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

  • Мод

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

  • Піфагор

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

  • Радіальні

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

  • Випадкові

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

  • Сканування

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

  • Спіраль (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 .
Джейсон C

Він мав на увазі просто тизер, але я редагував це, боячись не потрапити під прапор, що, мабуть, сталося :)
Марк Єронімус

2
Навіть аварії виглядають приємно :). Кольоровий куб здається дуже хорошою ідеєю, а ваша швидкість візуалізації дивовижна порівняно з моєю. Деякі конструкції на allrgb мають хороший опис, наприклад allrgb.com/dla. Я б хотів, щоб у мене було більше часу, щоб зробити більше експериментів, є так багато можливостей ...
fejesjoco

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

Додано вихідний код та посилання на бібліотеку Digisoft, так що ви можете фактично скласти мій код
Марк Єронімус

72

C ++ w / 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();
        }
    }

}

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

лемур

Я генерую щось подібне:

кислота Лемур

нестиснута версія тут: https://www.mediafire.com/?7g3fetvaqhoqgh8

На моєму комп’ютері потрібно приблизно 30 хвилин, щоб зробити зображення 4096 ^ 2, що є величезним поліпшенням за 32 дні, які моє перше виконання зайняло б.


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

5
Я люблю його панк брови!
Річка рівня Св.

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 Quicksort - інший алгоритм, ніж
Bubblesort

43

С

Створює вихор з незрозумілих мені причин з парними та непарними кадрами, що містять абсолютно різні вихори.

Це попередній перегляд перших 50 непарних кадрів:

попередній перегляд вихору

Зразок зображення, перетворений з PPM в демонстраційне повне кольорове покриття:

зразок зображення

Пізніше, коли все це змішується в сірий колір, ви можете побачити його спінінг: більше послідовності .

Код наступним чином. Для запуску додайте номер кадру, наприклад:

./vortex 35 > 35.ppm

Я використовував це, щоб отримати анімований GIF:

конвертувати -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
Ви знаєте, що це C, коли щось трапляється з "причин, яких я не розумію".
Ніт

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

8
Він вихровий, тому що ваша порівняльна функція не відповідає нерівності трикутника. Наприклад, r> b, b> g, g> r. Я навіть не можу перенести його на Java, оскільки її об'єднання покладається на цю саму властивість, тому я отримую виняток "Метод порівняння порушує його загальний контракт!"
Марк Єронімус

2
Я спробую, p->b * 6 - q->g;але якщо він вирве вихор, не виправити це!

4
+1 з причин, яких я не розумію.
Джейсон C

40

Java

Варіації вибору кольору 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у x / y. Граючи з різними розмірами, мені подобається радіус 32 пікселя, оскільки він розмиває лінії, не пересуваючи занадто багато:

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


3
оо ці фотографії найкрасивіші
sevenseacat

Це справді чудово 😍
Метью

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

Алгоритм

Почніть з квадратів 4х4 з усіх можливих комбінацій із 32 значень зеленого та синього, в х, у. формату, розмір площі 128х128 Кожен квадрат 4х4 має 16 пікселів, тому зробіть дзеркальне зображення поруч із ним, щоб надати 32 пікселі кожної можливої ​​комбінації зеленого та синього кольорів на зображення нижче.

(химерно повний зелений виглядає яскравіше, ніж повний синій. Це має бути оптичною ілюзією. Вияснено в коментарях)

На лівому квадраті додайте до червоного значення 0-15. Для правого квадрата XOR ці значення з 16, щоб значення 16-31.

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

Вихід 256х128

Це дає вихід у верхньому зображенні внизу.

Однак кожен піксель відрізняється від свого дзеркального зображення лише найбільш значущим бітом червоного значення. Отже, я можу застосувати умову зі змінною c, щоб повернути XOR, що має такий же ефект, як і обмін цими двома пікселями.

Приклад цього наведено в нижньому зображенні нижче (якщо ми відмежуємо рядок коду, який зараз коментується.)

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

512 x 512 - данина Меріліну Енді Уорхолу

Натхненний відповіддю Квінкунса на це запитання зі «злою усмішкою» у вільних червоних колах, ось моя версія відомої картини. Оригінал насправді мав 25 кольорових Мерілінів та 25 чорно-білих Мерілінів і був даниною Уорхола Мерілін після її передчасної смерті. Дивіться http://en.wikipedia.org/wiki/Mrelyn_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% одного ядра мого процесора, навіть якщо він закінчив малювати. Наскільки я можу сказати, що немає функції сну, все, що ви можете зробити, це зменшити частоту опитування.

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-бітні кольори (5р, 6г, 5б) на кривій Гільберт у 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
Nayuki

30

Java

Маючи кілька варіантів моєї іншої відповіді, ми можемо отримати дуже цікаві результати.

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
Перше зображення виглядає як фотографія процесора чи пам'яті
Nick T

@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
Nick T

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

Це справді круто; вони ніби нагадують мені розбиту аркадну гру чи інше.
Джейсон C

29

Java

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

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

Клацніть для зображення 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
John P

2
Виникла проблема із завантаженням.
SuperJedi224

28

Скала

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

Вихід 512 x 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, використовуючи петлі в коді, я вирішив створити плоский файл для читання кольорів HSL. Що я зробив, це створити кожен RGB-колір у Список, потім я замовив Hue, Luminosity, потім Saturation. Потім я зберег Список у текстовому файлі. ColorData - це лише невеликий клас, про який я писав, який приймає колір RGB, а також зберігає еквівалент HSL. Цей код є ВЕЛИЧЕЗНИМ ПОЗИЦІЮ ОЗУ. Використовується близько 4 ГБ оперативної пам’яті, лол.

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, це означає, що ви шукаєте квадрат 3x3, із вказаним пікселем праворуч у центрі.

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 завжди спочатку здійснюватиме повний пошук у квадраті. Я знаю, що міг би оптимізувати це, щоб виконати пошук по периметру лише після заповнення початкового повного квадрата. Я просто ще не обійшов цю оптимізацію, і навіть без цього код досить швидкий. Коментовані рядки "блокування" полягають у тому, що я в один момент використовував Parallel.ForEach, але зрозумів, що мені потрібно написати більше коду, ніж я хотів для цього lol.

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, використовуючи кожен колір, який швидко виблискує на графічному процесорі, з двома простими сценаріями шейдерів. Вихід нудний, але придумав, що хтось може виявити це цікавим і придумати кілька цікавих ідей:

Вершина Шейдер

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 днів .. і його все ще оптимізують вихід. Він в основному збігається з деяким місцевим мінімумом, але, мабуть, все ще вдосконалює:введіть тут опис зображення

Редагувати: 8.12.17 - Я написав новий алгоритм випадкової ходьби - в основному ви вказуєте кількість "ходунків", але замість того, щоб ходити випадковим чином - вони випадковим чином виберуть іншу ходунку і або уникатимуть їх (вибирають наступний доступний піксель, віддалений найдалі ) - або йдіть до них (виберіть наступний доступний піксель найближчий до них). Приклад виведення сірого кольору є тут (я буду робити повне кольорове зображення 4096x4096 після того, як підключу забарвлення!):введіть тут опис зображення


4
Трохи запізнілий, але ласкаво просимо до PPCG! Це відмінне перше повідомлення.
спагето

1
Дякую! Я з нетерпінням чекаю завершити більше викликів! Останнім часом я займаюся кодуванням зображень, це моє нове захоплення
applejacks01,

Ого, це дивовижні; Я радий, що сьогодні повернувся до цієї посади і перевірив усі пізніші речі.
Джейсон C

Дякую! Я фактично роблю якийсь генетичний алгоритм кодування зараз, щоб отримати цікаві градієнти. В основному візьміть 10000 кольорів, утворюючи сітку 100х100. Для кожного пікселя знайдіть сусідні пікселі. Для кожного пройдіть відстань CIEDE2000. Підсумуйте це. Підсумуйте всі 10000 пікселів. Генетичний алгоритм намагається зменшити цю загальну суму. Його повільний, але для зображення 20x20 його вихід справді цікавий
applejacks01

Я дуже люблю вихід цього рішення.
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
Схоже на якісь божевільні звукові форми
Марк Єронімус

19

Java

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

Крім того, мій перший гольф-код вперше. :)

(4 к. Зображення були занадто великими для моєї невеликої швидкості завантаження. Я спробував завантажити одне, але через одну годину він не завантажився. Ви можете створити свої власні.)

Впритул:

Створює зображення за 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

Математика

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]

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

256x128 2x

Оригінальне зображення 256х128

Редагувати:

замінивши 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);
    }
}

256x128 малюнок, кольори відсортовані червоний-> зелений-> синій
RGB сортовані кольори

256x128 малюнок, кольори відсортовані синьо-> зелений-> червоний
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 #

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

Ось невеликий зразок випуску, який я створив до цього часу, я працюю над 4-кратним візуалізацією, але, сподіваюся, це займе день до кінця.

Ось зразок специфікації на 256x128:

Spec Render

Деякі великі зображення з ще розумним часом візуалізації:

Візуалізація на 360 x 240

Другий пробіг на 360 x 240 створив набагато більш плавне зображення

Візуалізація №2 на 360 x 240

Після підвищення продуктивності я зміг запустити HD-рендер, який зайняв 2 дні, я ще не відмовився від 4k, але це може зайняти тижні.

Візуалізація HD

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 кольори на компонент?
Марк Єронімус

Яка мова це? Рандомізація сортування списку та Random - це погана ідея незалежно, тому що залежно від алгоритму та впровадження це може спричинити необ’єктивний результат або виняток, який стверджує, що "Comparison method violates its general contract!": тому що в договорі зазначено це (x.compareTo(y)>0 && y.compareTo(z)>0) implies x.compareTo(z)>0. Щоб рандомізувати список, використовуйте деякий наданий метод Shuffle. ( colors.Shuffle()?)
Марк Єронім

@MarkJeronimus Я визнаю, що я пропустив специфікацію щодо зображення 256х128, я буду робити прості візуалізації, використовуючи ці розміри, я зосередив увагу на кожному пікселі, це унікальний кольоровий аспект виклику та більші рендери, як це робили інші матеріали.
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 кольорів, по одному на кожен піксель.

EDIT: Я створив версію, яка відображає зображення 4096X4096, використовуючи 2 ^ 24 кольори. Кольори також "відображені", тому вони роблять приємні, плавні градієнти. Я надам лише посилання на них, оскільки вони величезні (> 28 Мб)

Веселка Ленґтона велика, правило LR

Веселка Ленґтона велика, правило LLRR

// Кінець редагування.

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

Langton's Rainbow LR

Ось LLRR:

LLRR Langton's Rainbow

Нарешті, цей використовує набір правил LRRRRRLLR:

Langton's Rainbow 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 вбивство загальнодоступних папок.
користувач2428118

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 Ok, відредагований
дверна ручка

Значно краще!!!!!!!

1
Що буде, якщо ви змінили четвертий на останній рядок }.shuffle }.shuffle }.shuffle?
Джон Одом

6
@John Erm, помилка синтаксису, ймовірно?
Дверна ручка

14

Пітон

плазма

Використовуючи пітон для сортування кольорів за яскравістю, генерування яскравості та вибору найбільш відповідного кольору. Пікселі повторюються у довільному порядку, так що менш сприятливі збіги яскравості, які, природно, трапляються, коли список доступних кольорів стає меншим, рівномірно розподіляються по всій картині.

#!/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

Java

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 Так. Я називаю це дуже красивим. Мені здається дивовижним, що всі кольори можуть бути впорядковані у велику сіру плямку. Мені здається, що крапля є цікавішою, коли я збільшую зображення. З трохи детальнішими відомостями ви можете бачити, наскільки невипадковий rng 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:

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

z:

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

Y:

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

Я б хотів, щоб я міг зрозуміти, що викликає непарні лінії в перших трьох


2
Ці дивні лінії, ймовірно, є упередженням якогось способу або способу пошуку (двійковий пошук /
швидкий вибір

Мені насправді дуже подобаються лінії.
Джейсон C

11

Java

Це була набагато краща ідея. Це дуже короткий код 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 x 4096

Вихід (скріншот, занадто великий інакше):

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

Вихід зі злим усмішкою, намальованим на ньому від руки-червоні кола:

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


2
Посилання на оригінал, щоб я міг перевірити дійсність (порахувати кольори)
Марк Єронімус

2
Лол! Я забув, що можу запустити Java-код. Перше зображення проходить, і я не можу відтворити друге зображення (lol) ☺
Марк Єронімус

16
Усі вільні кола мають один і той же колір, дискваліфіковані. : P
Нік Т

3
@Quincunx +1, якщо ви можете намалювати страшне обличчя і все-таки дотримати вимоги до кольору!
Джейсон C

2
@JasonC Дивіться мою відповідь. Кредит йде на Quincunx за натхненням.
Річка Рівня Св.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.