Obrazy we wszystkich kolorach


433

Podobne do zdjęć na allrgb.com , obrazy, w których każdy piksel ma unikalny kolor (żaden kolor nie jest używany dwa razy i nie brakuje koloru).

Podaj program, który generuje taki obraz, wraz ze zrzutem ekranu lub plikiem wyjściowym (prześlij jako PNG).

  • Utwórz obraz wyłącznie algorytmicznie.
  • Obraz musi mieć rozmiar 256 × 128 (lub siatkę, którą można wykonać zrzut ekranu i zapisać w rozmiarze 256 × 128)
  • Użyj wszystkich 15-bitowych kolorów *
  • Niedozwolone jest wprowadzanie danych zewnętrznych (również brak zapytań internetowych, adresów URL i baz danych)
  • Żadne osadzone obrazy nie są dozwolone (kod źródłowy, który jest obrazem, jest w porządku, np. Piet )
  • Dithering jest dozwolony
  • To nie jest konkurs na krótki kod, chociaż może wygrać głosy.
  • Jeśli naprawdę masz ochotę na wyzwanie, zrób 512 × 512, 2048 × 1024 lub 4096 × 4096 (w krokach co 3 bity).

Punktacja odbywa się przez głosowanie. Głosuj na najpiękniejsze obrazy wykonane za pomocą najbardziej eleganckiego kodu i / lub interesującego algorytmu.

Algorytmy dwuetapowe, w których najpierw generujesz ładny obraz, a następnie dopasowujesz wszystkie piksele do jednego z dostępnych kolorów, są oczywiście dozwolone, ale nie zdobędą punktów elegancji.

* 15-bitowe kolory to 32768 kolorów, które można uzyskać przez zmieszanie 32 czerwonych, 32 zielonych i 32 niebieskich kolorów, wszystkie w jednakowych odległościach i jednakowych zakresach. Przykład: w obrazach 24-bitowych (8 bitów na kanał) zakres na kanał wynosi 0..255 (lub 0..224), więc podziel go na 32 równo rozmieszczone odcienie.

Aby być bardzo wyraźnym, układ pikseli obrazu powinien być permutacją, ponieważ wszystkie możliwe obrazy mają te same kolory, tylko w różnych lokalizacjach pikseli. Dam trywialną permutację tutaj, która wcale nie jest piękna:

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

wprowadź opis zdjęcia tutaj

Zwycięzca

Ponieważ 7 dni minęło, ogłaszam zwycięzcę

Jednak w żadnym wypadku nie myśl, że to już koniec. Ja i wszyscy czytelnicy zawsze cieszą się bardziej niesamowitymi projektami. Nie przestawaj tworzyć.

Zwycięzca: fejesjoco z 231 głosów


8
Co powiesz na „Dithering jest dozwolony”? Czy to wyjątek od reguły „każdy piksel ma unikalny kolor”? Jeśli nie, to na co zezwalasz, co w innym przypadku było zabronione?
Peter Taylor,

1
Oznacza to, że możesz umieszczać kolory we wzorach, więc oglądane gołym okiem łączą się w inny kolor. Na przykład zobacz obraz „wyraźnie wszystkie RGB” na stronie allRGB i wiele innych tam.
Mark Jeronimus

8
Uważam, że twój trywialny przykład permutacji jest całkiem przyjemny dla oka.
Jason C

2
@ Zom-B Człowieku, kurwa kocham ten post. Dzięki!
Jason C

7
Piękne wyniki / odpowiedzi!
EthanB

Odpowiedzi:


534

DO#

Umieszczam losowy piksel na środku, a następnie zaczynam umieszczać losowe piksele w okolicy, która najbardziej do nich przypomina. Obsługiwane są dwa tryby: przy minimalnym zaznaczeniu rozważany jest tylko jeden sąsiedni piksel na raz; przy średnim wyborze wszystkie (1..8) są uśredniane. Minimalna selekcja jest nieco głośna, średnia selekcja jest oczywiście bardziej rozmyta, ale oba wyglądają jak obrazy. Po pewnej edycji, oto aktualna, nieco zoptymalizowana wersja (wykorzystuje nawet przetwarzanie równoległe!):

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

256 x 128 pikseli, zaczynając od środka, minimalny wybór:

256 x 128 pikseli, zaczynając od lewego górnego rogu, minimalny wybór:

256 x 128 pikseli, zaczynając od środka, średni wybór:

Oto dwa 10-ramkowe animacje, które pokazują, jak działa minimalny i średni wybór (uznanie dla formatu gif za możliwość wyświetlenia go tylko w 256 kolorach):

Tryb minimalnego wyboru rośnie z małym frontem fali, takim jak kropelka, wypełniając wszystkie piksele w miarę upływu czasu. Jednak w trybie średnim, gdy dwie gałęzie o różnych kolorach zaczną rosnąć obok siebie, pojawi się mała czarna przerwa, ponieważ nic nie będzie wystarczająco blisko dwóch różnych kolorów. Ze względu na te luki czoło fali będzie o rząd wielkości większe, dlatego algorytm będzie o wiele wolniejszy. Ale to miłe, ponieważ wygląda jak rosnący koralowiec. Gdybym upuścił tryb średni, można by go nieco przyspieszyć, ponieważ każdy nowy kolor jest porównywany z każdym istniejącym pikselem około 2-3 razy. Nie widzę innych sposobów na jego optymalizację, myślę, że jest wystarczająco dobry.

Dużą atrakcją jest renderowanie 512 x 512 pikseli, środkowy początek, minimalny wybór:

Po prostu nie mogę przestać się tym bawić! W powyższym kodzie kolory są sortowane losowo. Jeśli w ogóle nie sortujemy lub nie sortujemy według hue ( (c1, c2) => c1.GetHue().CompareTo(c2.GetHue())), otrzymujemy odpowiednio (zarówno środkowy początek, jak i minimalny wybór):

Kolejna kombinacja, w której forma koralowa jest przechowywana do końca: odcień uporządkowany ze średnim wyborem, z animowanym 30-ramkowym animowaniem:

AKTUALIZACJA: JEST GOTOWE !!!

Chciałeś hi-res, ja chciałem hi-res, byłeś niecierpliwy, ledwo spałem. Teraz jestem podekscytowany, że mogę ogłosić, że w końcu jest gotowy, jakość produkcji. I wypuszczam go z wielkim hukiem, niesamowitym filmem na YouTube w jakości 1080p! Kliknij tutaj, aby obejrzeć film , sprawmy, aby wirus promował styl maniaków. Publikuję też rzeczy na moim blogu pod adresem http://joco.name/ , będzie techniczny post o wszystkich interesujących szczegółach, optymalizacjach, sposobie, w jaki zrobiłem film itp. Na koniec dzielę się źródłem kod na licencji GPL. Stało się ogromne, więc odpowiedni hosting jest najlepszym miejscem do tego, nie będę już edytować powyższej części mojej odpowiedzi. Pamiętaj, aby skompilować w trybie wydania! Program dobrze skaluje się do wielu rdzeni procesora. Renderowanie 4Kx4K wymaga około 2-3 GB pamięci RAM.

Mogę teraz renderować ogromne obrazy w ciągu 5-10 godzin. Mam już kilka renderów 4Kx4K, opublikuję je później. Program bardzo się rozwinął, dokonano niezliczonych optymalizacji. Uczyniłem go również przyjaznym dla użytkownika, aby każdy mógł go łatwo używać, ma ładną linię poleceń. Program jest również deterministycznie losowy, co oznacza, że ​​możesz użyć losowego materiału siewnego i za każdym razem wygeneruje ten sam obraz.

Oto kilka dużych renderów.

Moja ulubiona 512:


(źródło: joco.name )

2048, które pojawiają się w moim filmie :


(źródło: joco.name )


(źródło: joco.name )


(źródło: joco.name )


(źródło: joco.name )

Pierwsze 4096 renderuje (TODO: są przesyłane, a moja strona nie jest w stanie obsłużyć dużego ruchu, więc są tymczasowo przenoszone):


(źródło: joco.name )


(źródło: joco.name )


(źródło: joco.name )


(źródło: joco.name )


25
To jest fajne!
Jaa-c

5
Bardzo fajnie :-D Teraz zrób kilka większych!
skrzypiące kostkowanie

20
Jesteś prawdziwym artystą! :)
AL

10
Ile kosztuje wydruk?
primo

16
Pracuję nad ogromnymi rendersami i filmem 1080p. Zajmie to godziny lub dni. Mam nadzieję, że ktoś będzie w stanie stworzyć wydruk z dużego renderowania. Lub nawet koszulka: kod z jednej strony, obraz z drugiej. Czy ktoś może to załatwić?
fejesjoco

248

Przetwarzanie

Aktualizacja!4096 x 4096 zdjęć!

Połączyłem mój drugi post z tym, łącząc oba programy razem.

Pełną kolekcję wybranych zdjęć można znaleźć tutaj, na Dropbox .(Uwaga: DropBox nie może generować podglądów obrazów 4096 x 4096; wystarczy je kliknąć, a następnie „Pobierz”).

Jeśli spojrzysz tylko na jedno spojrzenie (na kafelki)! Tutaj jest pomniejszone (i wiele innych poniżej), oryginalne 2048x1024:

enter image description here

Ten program działa poprzez chodzenie ścieżkami z losowo wybranych punktów w sześcianie kolorów, a następnie rysowanie ich w losowo wybranych ścieżkach na obrazie. Istnieje wiele możliwości. Dostępne opcje to:

  • Maksymalna długość ścieżki kostki koloru.
  • Maksymalny krok do przejścia przez kolorową kostkę (większe wartości powodują większą wariancję, ale minimalizują liczbę małych ścieżek pod koniec, gdy wszystko się zaciska).
  • Układanie obrazu
  • Obecnie istnieją dwa tryby ścieżki obrazu:
    • Tryb 1 (tryb tego oryginalnego postu): Znajduje blok nieużywanych pikseli na obrazie i renderuje do tego bloku. Bloki mogą być losowo rozmieszczone lub uporządkowane od lewej do prawej.
    • Tryb 2 (tryb mojego drugiego postu, który połączyłem z tym): Wybiera losowy punkt początkowy na obrazie i idzie ścieżką przez niewykorzystane piksele; może chodzić po używanych pikselach. Opcje dla tego trybu:
      • Zestaw wskazówek do przejścia (ortogonalny, ukośny lub oba).
      • Określa, czy zmienić kierunek (obecnie zgodnie z ruchem wskazówek zegara, ale kod jest elastyczny) po każdym kroku, czy zmienić kierunek tylko po napotkaniu zajętego piksela.
      • Możliwość losowego zmieniania kolejności zmian kierunku (zamiast w prawo).

Działa dla wszystkich rozmiarów do 4096 x 4096.

Kompletny szkic przetwarzania można znaleźć tutaj: Tracer.zip

Wkleiłem wszystkie pliki w tym samym bloku kodu poniżej, aby zaoszczędzić miejsce (nawet wszystkie w jednym pliku, nadal jest to prawidłowy szkic). Jeśli chcesz użyć jednego z ustawień wstępnych, zmień indeks w gPresetprzypisaniu. Jeśli uruchomisz to w Przetwarzaniu, możesz nacisnąć rpodczas działania, aby wygenerować nowy obraz.

  • Aktualizacja 1: Zoptymalizowany kod do śledzenia pierwszego nieużywanego koloru / piksela i nie przeszukiwania znanych pikseli; skrócono czas generowania 2048x1024 z 10-30 minut do około 15 sekund oraz 4096x4096 z 1-3 godzin do około 1 minuty. Upuść źródło pola i źródło poniżej zaktualizowane.
  • Aktualizacja 2: Naprawiono błąd, który uniemożliwiał generowanie obrazów 4096 x 4096.
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;
  }

}

Oto pełny zestaw 256 x 128 zdjęć, które lubię:

Tryb 1:

Mój ulubiony z oryginalnego zestawu (max_path_length = 512, path_step = 2, losowy, wyświetlany 2x, link 256x128 ):

enter image description here

Inne (lewe dwa uporządkowane, prawe dwa losowe, górna długość dwóch ścieżek ograniczona, dolne dwa nieograniczone):

ordlimit randlimit ordnolimit randnolimit

Ten można kafelkować:

randtile

Tryb 2:

diamonds flowers boxfade diagover bigdiamonds boxes2 shards

Te można kafelkować:

bigtile diamondtile giftwrap

Wybory 512x512:

Diamenty do układania, moje ulubione z trybu 2; w tym możesz zobaczyć, jak ścieżki przebiegają wokół istniejących obiektów:

enter image description here

Większy krok ścieżki i maksymalna długość ścieżki, do kafelkowania:

enter image description here

Tryb losowy 1 z możliwością kafelkowania:

enter image description here

Więcej opcji:

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

Wszystkie renderingi 512x512 można znaleźć w folderze dropbox (* _64.png).

2048x1024 i 4096x4096:

Są zbyt duże, aby je osadzić, a wszystkie hosty obrazów, które znalazłem, upuszczają je do 1600 x 1200. Obecnie renderuję zestaw obrazów o wymiarach 4096 x 4096, więc wkrótce będzie dostępnych więcej. Zamiast dołączać tutaj wszystkie linki, po prostu sprawdź je w folderze Dropbox (* _128.png i * _256.png, uwaga: te 4096 x 4096 są zbyt duże dla podglądu Dropbox, wystarczy kliknąć „pobierz”). Oto niektóre z moich ulubionych:

2048x1024 dużych diamentów do kafelkowania (ten sam, do którego podłączyłem na początku tego postu)

2048x1024 diamentów (uwielbiam ten!), Pomniejszone :

enter image description here

4096 x 4096 dużych diamentów do kafelkowania (w końcu! Kliknij link „pobierz” w łączu Dropbox; jest zbyt duży dla ich podglądu), przeskalowany w dół:

4096x4096 big tileable diamonds

4096 x 4096 tryb losowy 1 : enter image description here

4096x4096 kolejny fajny

Aktualizacja: Gotowy zestaw obrazów 2048x1024 jest gotowy i znajduje się w polu rozwijanym. Zestaw 4096 x 4096 powinien zostać wykonany w ciągu godziny.

Jest mnóstwo dobrych, ciężko mi wybrać, które z nich opublikować, więc sprawdź link do folderu!


6
Przypomina mi zbliżenia niektórych minerałów.
Morwenn

3
To nie jest część konkursu, ale pomyślałem, że to trochę fajne ; Zastosowałem duże rozmycie gaussowskie i automatyczny kontrast w jednym z losowych zdjęć w trybie 1 w Photoshopie, dzięki czemu było to miłe tło pulpitu.
Jason C

2
whoa, to są fajne zdjęcia!
sevenseacat

2
Przypomina mi tekstury Gustava Klimta.
Kim

2
Czy wiesz, że możesz umieszczać linki do zdjęć w Dropbox? Wystarczy skopiować URL pobierania, usuń dl=1, a token_hash=<something>część i zrobić link do zdjęcia tak: [![Alt text of my small preview image](https://i.stack.imgur.com/smallpreview.png)](https://dl.dropbox.com/linktoyourfullsiz‌​eimage.png). Kolejna wskazówka: możesz kompresować swoje zdjęcia ( uzyskuję dobre wyniki dzięki TruePNG ( pobieranie )). Udało mi się zapisać 28,1% rozmiaru pliku na tym obrazie .
user2428118

219

Python w / PIL

Jest to oparte na fraktalu newtonowskim , szczególnie dla z → z 5 - 1 . Ponieważ istnieje pięć pierwiastków, a tym samym pięć punktów zbieżności, dostępna przestrzeń kolorów jest podzielona na pięć regionów w oparciu o Barwę. Poszczególne punkty są sortowane najpierw według liczby iteracji wymaganych do osiągnięcia punktu zbieżności, a następnie według odległości do tego punktu, przy czym wcześniejszym wartościom przypisuje się jaśniejszy kolor.

Aktualizacja: duże rendery 4096 x 4096 , hostowane na allrgb.com .

Oryginał (33,7 MB)

Zbliżenie na sam środek (rzeczywisty rozmiar):

Inny punkt obserwacyjny wykorzystujący te wartości:

xstart = 0
ystart = 0

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

Oryginał (32,2 MB)

I inny za pomocą tych:

xstart = 0.5
ystart = 0.5

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

Oryginał (27,2 MB)


Animacja

Na życzenie skompilowałem animację powiększenia.

Ogniskowy: ( 0,50051 , -0,50051 )
Współczynnik powiększenia: 2 1/5

Punkt centralny ma nieco dziwną wartość, ponieważ nie chciałem powiększać czarnej kropki. Współczynnik powiększenia jest wybierany w taki sposób, że podwaja się co 5 klatek.

Teaser 32x32:

Wersję 256x256 można zobaczyć tutaj:
http://www.pictureshack.org/images/66172_frac.gif (5,4 MB)

Mogą istnieć punkty, które matematycznie powiększają „na siebie”, co pozwoliłoby na nieskończoną animację. Jeśli mogę je zidentyfikować, dodam je tutaj.


Źródło

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
Nareszcie fraktal :) Uwielbiam je. Również ten zielony w 144 stopniach jest moim ulubionym kolorem (w przeciwieństwie do czystej zieleni w 120 stopniach, która jest po prostu nudna).
Mark Jeronimus

2
Nie wiem, właściwie bardziej lubię wersje AllRGB; potrzeba korzystania z pełnej przestrzeni luminancji ładnie podkreśla gradienty.
Ilmari Karonen

2
+1 Wreszcie kilka dobrych fraktali! Ten ostatni jest moim ulubionym. Powinieneś zrobić zbliżenie wideo! (@Quincunx: Też widziałem; głosowałem od 1 dnia!)
Jason C

1
@JasonC Dodałem animację;)
primo

2
@primo Wiem, że się spóźniłem, ale chciałem tylko powiedzieć, że te zdjęcia są spektakularne.
Ashwin Gupta,

130

Wpadłem na ten pomysł z algorytmu użytkownika fejesjoco i chciałem trochę zagrać, więc zacząłem pisać własny algorytm od zera.

Publikuję to, ponieważ uważam, że jeśli mogę zrobić coś lepszego * niż najlepsze z was, nie sądzę, że to wyzwanie jest jeszcze zakończone. Dla porównania, na allRGB jest kilka oszałamiających projektów, które uważam za znacznie wykraczające poza poziom tutaj osiągnięty i nie mam pojęcia, jak to zrobili.

*) nadal będą podejmowane głosy

Ten algorytm:

  1. Zacznij od (kilku) nasion, z kolorami możliwie najbliższymi czerni.
  2. Zachowaj listę wszystkich pikseli, które nie są odwiedzane i są 8 połączone z odwiedzanym punktem.
  3. Wybierz losowy ** punkt z tej listy
  4. Oblicz średni kolor wszystkich obliczonych pikseli [Edytuj ... w kwadracie 9x9 przy użyciu jądra Gaussa] 8-połączonych z nim (to jest powód, dla którego wygląda tak gładko) Jeśli nie zostaną znalezione, weź czarny.
  5. w sześcianie 3 x 3 x 3 wokół tego koloru, wyszukaj nieużywany kolor.
    • Kiedy znajdziesz wiele kolorów, wybierz najciemniejszy.
    • Kiedy znajdziesz wiele równie ciemnych kolorów, wybierz jeden z nich losowo.
    • Gdy nic nie zostanie znalezione, zaktualizuj zakres wyszukiwania do 5 x 5 x 5, 7 x 7 x 7 itd. Powtórz od 5.
  6. Rysuj piksel, aktualizuj listę i powtarzaj od 3

Eksperymentowałem również z różnymi prawdopodobieństwami wyboru punktów kandydujących na podstawie zliczenia liczby odwiedzonych sąsiadów wybranego piksela, ale spowolnił on algorytm, nie czyniąc go ładniejszym. Obecny algorytm nie wykorzystuje prawdopodobieństwa i wybiera losowy punkt z listy. Powoduje to, że punkty z dużą liczbą sąsiadów szybko się zapełniają, co sprawia, że ​​jest to po prostu rosnąca solidna kula o niewyraźnej krawędzi. Zapobiega to również niedostępności sąsiednich kolorów, jeśli szczeliny miałyby zostać wypełnione w późniejszym czasie.

Obraz jest toroidalny.

Jawa

Pobierz: com.digitalmodularbiblioteka

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
  • oryginalne 1 ziarno
  • 1 sekunda

enter image description here

  • 2048 × 1024
  • lekko sąsiadująco na pulpicie 1920 × 1080
  • 30 sekund
  • negatyw w Photoshopie

enter image description here

  • 2048 × 1024
  • 8 nasion
  • 27 sekund

enter image description here

  • 512 × 512
  • 40 losowych nasion
  • 6 sekund

enter image description here

  • 4096 × 4096
  • 1 ziarno
  • Smugi stają się znacznie ostrzejsze (ponieważ wyglądają, jakby mogły pokroić rybę w sashimi)
  • Wyglądało na to, że zakończyło się za 20 minut, ale ... z jakiegoś powodu nie udało się go ukończyć, więc teraz uruchamiam 7 instancji równolegle przez noc.

[Patrz poniżej]

[Edytuj]
** Odkryłem, że moja metoda wyboru pikseli nie była całkowicie losowa. Myślałem, że losowa permutacja przestrzeni wyszukiwania będzie losowa i szybsza niż prawdziwa losowa (ponieważ punkt nie zostanie wybrany dwukrotnie przypadkowo. Jednak w jakiś sposób, zastępując go prawdziwą losową, konsekwentnie dostaję więcej plam szumowych na moim obrazie.

[usunięto kod wersji 2, ponieważ przekroczyłem limit 30 000 znaków]

enter image description here

  • Zwiększono początkową kostkę wyszukiwania do 5 x 5 x 5

enter image description here

  • Jeszcze większy, 9x9x9

enter image description here

  • Wypadek 1. Wyłączono permutację, aby przestrzeń wyszukiwania była zawsze liniowa.

enter image description here

  • Wypadek 2. Wypróbowałem nową technikę wyszukiwania, używając kolejki fifo. Nadal muszę to przeanalizować, ale pomyślałem, że warto się tym podzielić.

enter image description here

  • Zawsze wybieraj spośród X nieużywanych pikseli od środka
  • X wynosi od 0 do 8192 w krokach 256

Nie można załadować obrazu: „Ups! Coś złego się stało! To nie ty, to my. To nasza wina”. Obraz jest po prostu za duży, aby zrobić imgur. Próbuję gdzie indziej ...

enter image description here

Eksperymentując z pakietem harmonogramu znalazłem w digitalmodularbibliotece, aby ustalić kolejność obsługiwania pikseli (zamiast dyfuzji).

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;
    }
}
  • Kątowy (8)

enter image description here

  • Kątowy (64)

enter image description here

  • CRT

enter image description here

  • Dygotać

enter image description here

  • Kwiat (5, X), gdzie X waha się od 0,5 do 20 w krokach X = X × 1,2

enter image description here

  • Mod

enter image description here

  • Pitagoras

enter image description here

  • Promieniowy

enter image description here

  • Losowy

enter image description here

  • Scanline

enter image description here

  • Spirala (X), gdzie X wynosi od 0,1 do 200 w krokach X = X × 1,2
  • Możesz zobaczyć, że waha się między radialnym a kątowym (5)

enter image description here

  • Rozdzielać

enter image description here

  • SquareSpiral

enter image description here

  • XOR

enter image description here

Nowy pokarm dla oka

  • Efekt wyboru koloru przez max(r, g, b)

enter image description here

  • Efekt wyboru koloru przez min(r, g, b)
  • Zauważ, że ten ma dokładnie takie same funkcje / szczegóły jak powyższy, tylko w różnych kolorach! (to samo losowe ziarno)

enter image description here

  • Efekt wyboru koloru przez max(r, min(g, b))

enter image description here

  • Efekt wyboru koloru według wartości szarości 299*r + 436*g + 114*b

enter image description here

  • Efekt wyboru koloru przez 1*r + 10*g + 100*b

enter image description here

  • Efekt wyboru koloru przez 100*r + 10*g + 1*b

enter image description here

  • Szczęśliwe wypadki po 299*r + 436*g + 114*bprzepełnieniu 32-bitowej liczby całkowitej

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

  • Wariant 3, z szarą wartością i programem do planowania radialnego

enter image description here

  • Zapomniałem, jak to stworzyłem

enter image description here

  • CRT Scheduler miał również szczęśliwy błąd przepełnienia liczb całkowitych (zaktualizował ZIP), co spowodowało, że zaczął się w połowie, z obrazami 512 × 512, zamiast w środku. Tak to powinno wyglądać:

enter image description here enter image description here

  • InverseSpiralScheduler(64) (Nowy)

enter image description here

  • Kolejny XOR

enter image description here

  • Pierwsze udane renderowanie 4096 po usunięciu błędu. Myślę, że to była wersja 3 SpiralScheduler(1)lub coś w tym rodzaju

enter image description here (50 MB !!)

  • Wersja 1 4096, ale przypadkowo pozostawiłem włączone kryteria koloru max()

enter image description here (50 MB !!)

  • 4096, teraz z min()
  • Zauważ, że ten ma dokładnie takie same funkcje / szczegóły jak powyższy, tylko w różnych kolorach! (to samo losowe ziarno)
  • Czas: zapomniałem go nagrać, ale znacznik czasu pliku jest 3 minuty po obrazie wcześniej

enter image description here (50 MB !!)


Chłodny. Twój ostateczny obraz jest podobny do drugiego pomysłu, który miotałem, chociaż mam wrażenie, że mój nie będzie tak dobry. BTW, jest podobny fajny na allrgb.com/diffusive .
Jason C

To miał być tylko zwiastun, ale zredagowałem go w obawie przed
oznaczeniem

2
Nawet wypadki wyglądają ładnie :). Kolorowa kostka wydaje się być bardzo dobrym pomysłem, a twoje prędkości renderowania są niesamowite w porównaniu do moich. Niektóre projekty na allrgb mają dobry opis, na przykład allrgb.com/dla. Chciałbym mieć więcej czasu na więcej eksperymentów, jest tak wiele możliwości ...
fejesjoco

Prawie zapomniałem, właśnie przesłałem niektóre z moich dużych renderów. Myślę, że jeden z nich, tęczowy dym / rozlany atrament, jest lepszy niż cokolwiek innego na allrgb :). Zgadzam się, inni nie są tak zachwycający, dlatego zrobiłem film, aby zrobić z nich coś więcej :).
fejesjoco

Dodano kod źródłowy i link do biblioteki Digisoft, dzięki czemu możesz faktycznie skompilować mój kod
Mark Jeronimus

72

C ++ z / Qt

Widzę twoją wersję:

enter image description here

przy użyciu normalnego rozkładu kolorów:

enter image description here enter image description here

lub najpierw posortowane według koloru czerwonego / odcienia (z mniejszym odchyleniem):

enter image description here enter image description here

lub niektóre inne dystrybucje:

enter image description here enter image description here

Rozkład Cauchy'ego (hsl / red):

enter image description here enter image description here

posortowane cols według lekkości (hsl):

enter image description here

zaktualizowany kod źródłowy - tworzy 6. obraz:

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

Ładnie wykonane. Czy jednak nie może image.pixel(x, y) == 0zawieść i zastąpić pierwszego umieszczonego piksela?
Mark Jeronimus

@ Zom-B: może, ale wtedy ostatni będzie czarny, więc jest zgodny z zasadami ..
Jaa-c

Jednak nie ma problemu z regułami. Pomyślałem, że mogłeś to przegapić. Może również liczyć od 1. Kocham wasze pozostałe!
Mark Jeronimus

@ Zom-B: dzięki, mógłbym dodać jeszcze kilka, trochę mi się podoba: P
Jaa-c

Ten z dwoma okręgami i ten pod nim wyglądają trochę jak twarz małpy.
Jason C

64

W Javie:

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

}

i obraz wejściowy:

lemur

Generuję coś takiego:

acidLemur

wersja nieskompresowana tutaj: https://www.mediafire.com/?7g3fetvaqhoqgh8

Wykonanie obrazu 4096 ^ 2 zajmuje mojemu komputerowi około 30 minut, co stanowi ogromną poprawę w stosunku do 32 dni, które zajęłaby moja pierwsza implementacja.


1
ouch; 32 dni nie brzmiało zabawnie ..... średni algorytm w odpowiedzi fejesjocos na 4k przed optymalizacją
zajęłby

5
Uwielbiam jego punkowe brwi!
Level River St

45

Java z BubbleSort

(zwykle Bubblesort nie jest tak lubiany, ale do tego wyzwania w końcu się przydał :) wygenerował linię ze wszystkimi elementami w odstępach 4096 kroków, a następnie przetasował; sortowanie przeszło i każdy z nich dodał 1 do swojej wartości podczas sortowania, dzięki czemu posortowano wartości i wszystkie kolory

Zaktualizowano kod źródłowy, aby usunąć te duże paski
(potrzebowałem trochę magii bitowej: 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;
    }
}

Wynik:

Stara wersja

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

podgląd wyników


Na stronie allRGB jest już dostępna wersja QuickSort.
Mark Jeronimus

1
@ Zom-B Quicksort to inny algorytm niż Bubblesort
masterX244

43

do

Z niezrozumiałych dla mnie powodów tworzy wir z parzystymi i nieparzystymi ramkami zawierającymi zupełnie inne wiry.

Oto podgląd pierwszych 50 nieparzystych klatek:

podgląd wirów

Przykładowy obraz przekonwertowany z PPM na wersję demonstracyjną pełnego pokrycia kolorów:

przykładowy obraz

Później, kiedy wszystko zmieniło się w szary, nadal widać, że wiruje: dłuższa sekwencja .

Kod w następujący sposób. Aby uruchomić, dołącz numer ramki, np .:

./vortex 35 > 35.ppm

Użyłem tego, aby uzyskać animowany GIF:

konwersja-opóźnienie 10 ls * .ppm | sort -n | xargs` -loop 0 vortex.gif
#include <stdlib.h>
#include <stdio.h>

#define W 256
#define H 128

typedef struct {unsigned char r, g, b;} RGB;

int S1(const void *a, const void *b)
{
    const RGB *p = a, *q = b;
    int result = 0;

    if (!result)
        result = (p->b + p->g * 6 + p->r * 3) - (q->b + q->g * 6 + q->r * 3);

    return result;
}

int S2(const void *a, const void *b)
{
    const RGB *p = a, *q = b;
    int result = 0;

    if (!result)
        result = p->b * 6 - p->g;
    if (!result)
        result = p->r - q->r;
    if (!result)
        result = p->g - q->b * 6;

    return result;
}

int main(int argc, char *argv[])
{
    int i, j, n;
    RGB *rgb = malloc(sizeof(RGB) * W * H);
    RGB c[H];

    for (i = 0; i < W * H; i++)
    {
        rgb[i].b = (i & 0x1f) << 3;
        rgb[i].g = ((i >> 5) & 0x1f) << 3;
        rgb[i].r = ((i >> 10) & 0x1f) << 3;
    }

    qsort(rgb, H * W, sizeof(RGB), S1);

    for (n = 0; n < atoi(argv[1]); n++)
    {
        for (i = 0; i < W; i++)
        {
            for (j = 0; j < H; j++)
                c[j] = rgb[j * W + i];
            qsort(c, H, sizeof(RGB), S2);
            for (j = 0; j < H; j++)
                rgb[j * W + i] = c[j];
        }

        for (i = 0; i < W * H; i += W)
            qsort(rgb + i, W, sizeof(RGB), S2);
    }

    printf("P6 %d %d 255\n", W, H);
    fwrite(rgb, sizeof(RGB), W * H, stdout);

    free(rgb);

    return 0;
}

53
Wiesz, że to C, gdy coś się dzieje z „powodów, których nie rozumiem”.
Nit

2
Tak, zwykle wiem, czego się spodziewać, ale tutaj bawiłem się, żeby zobaczyć, jakie wzory mogę uzyskać, i pojawiła się ta niekończąca się sekwencja porządku w chaosie.

8
Wiruje, ponieważ funkcja porównywania nie jest zgodna z nierównością trójkąta. Na przykład r> b, b> g, g> r. Nie mogę nawet przenieść go na Javę, ponieważ jest on połączony z tą samą właściwością, więc otrzymuję wyjątek „Metoda porównawcza narusza ogólną umowę!”
Mark Jeronimus

2
Spróbuję, p->b * 6 - q->g;ale jeśli zniszczy wir, nie naprawię tego!

4
+1 z powodów, których nie rozumiem.
Jason C

40

Jawa

Warianty próbnika kolorów w 512x512. Elegancki kod nie jest , ale lubię ładne zdjęcia:

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

Jak napisano, generuje:

bez odtwarzania losowego

Jeśli go uruchomisz shuffle_block = true, tasuje kolory w każdym bloku 64x64:

blokowanie losowe

W przeciwnym razie, jeśli go uruchomisz shuffle_radius > 0, tasuje każdy piksel losowym pikselem shuffle_radiusw x / y. Po graniu w różnych rozmiarach podoba mi się promień 32 pikseli, ponieważ rozmazuje linie bez przesuwania zbyt wiele rzeczy:

wprowadź opis zdjęcia tutaj


3
ooh te zdjęcia są najładniejsze
sevenseacat

To są naprawdę świetne 😍
Matthew

37

Przetwarzanie

Właśnie zaczynam pracę z C (programowałem w innych językach), ale trudno mi było podążać za grafiką w Visual C, więc pobrałem ten program do przetwarzania używany przez @ace.

Oto mój kod i mój algorytm.

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

Algorytm

Zacznij od kwadratów 4x4 wszystkich możliwych kombinacji 32 wartości zielonego i niebieskiego, w x, y. format, tworząc kwadrat 128 x 128 Każdy kwadrat 4x4 ma 16 pikseli, więc zrób lustrzane odbicie obok niego, aby uzyskać 32 piksele każdej możliwej kombinacji zieleni i niebieskiego, na obraz poniżej.

(dziwnie pełna zieleń wygląda jaśniej niż pełna błękit. To musi być złudzenie optyczne. wyjaśnione w komentarzach)

W lewym kwadracie dodaj czerwone wartości 0-15. Dla prawego kwadratu XOR te wartości z 16, aby wartości 16-31.

wprowadź opis zdjęcia tutaj

Wyjście 256x128

Daje to wynik na górnym obrazku poniżej.

Jednak każdy piksel różni się od swojego obrazu lustrzanego tylko najbardziej znaczącym bitem czerwonej wartości. Mogę więc zastosować warunek za pomocą zmiennejc , aby odwrócić XOR, co ma taki sam efekt jak wymiana tych dwóch pikseli.

Przykład tego podano na dolnym obrazku poniżej (jeśli odkomentujemy wiersz kodu, który jest obecnie komentowany).

wprowadź opis zdjęcia tutaj

512 x 512 - Hołd dla Marylin Andy'ego Warhola

Zainspirowany odpowiedzią Quincunx na to pytanie ze „złym uśmiechem” w czerwonych kółkach odręcznych, oto moja wersja słynnego zdjęcia. Oryginał faktycznie miał 25 kolorowych Marylins i 25 czarno-białych Marylins i był hołdem Warhola dla Marylin po jej przedwczesnej śmierci. Zobacz http://en.wikipedia.org/wiki/Marilyn_Diptych

Zmieniłem różne funkcje po odkryciu, że Przetwarzanie renderuje te, których użyłem w 256 x 128 jako półprzezroczyste. Nowe są nieprzezroczyste.

I chociaż obraz nie jest całkowicie algorytmiczny, raczej go lubię.

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

}

wprowadź opis zdjęcia tutaj

512x512 Zmierzch nad jeziorem z górami w oddali

Tutaj w pełni algorytmiczny obraz. Bawiłem się zmieniając kolor, który moduluję w zależności od warunku, ale po prostu wracam do wniosku, że czerwony działa najlepiej. Podobnie jak w przypadku obrazu Marylin, najpierw rysuję góry, a następnie wybieram jasność z tego obrazu, aby zastąpić pozytywny obraz RGB podczas kopiowania do negatywnej połowy. Nieznaczna różnica polega na tym, że dno wielu gór (ponieważ wszystkie są narysowane tego samego rozmiaru) rozciąga się poniżej obszaru odczytu, więc obszar ten jest po prostu przycinany podczas procesu odczytu (co w ten sposób daje pożądane wrażenie gór o różnej wielkości. )

W tym używam komórki 8x4 32 czerwonych dla pozytywnych, a pozostałe 32 czerwonych dla negatywnych.

Zwróć uwagę na polecenie expicit frameRate (1) na końcu mojego kodu. Odkryłem, że bez tego polecenia Przetwarzanie zużyłoby 100% jednego rdzenia mojego procesora, nawet jeśli zakończyło rysowanie. O ile mogę stwierdzić, że nie ma funkcji uśpienia, wszystko, co możesz zrobić, to zmniejszyć częstotliwość odpytywania.

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

wprowadź opis zdjęcia tutaj


Ponieważ wcale nie jest w pełni niebieskozielony. To (0,217,217). Wszystkie 32 kombinacje są jednak obecne, tylko nierozciągnięte [0,255]. Edycja: Używasz kroków 7, ale nie mogę tego znaleźć w kodzie. To musi być proces przetwarzania.
Mark Jeronimus

@steveverrill Podczas przetwarzania możesz zrobić, save("filename.png")aby zapisać bieżący bufor ramki na obrazie. Obsługiwane są również inne formaty obrazów. Pozwoli ci to zaoszczędzić kłopotów z robieniem zrzutów ekranu. Obraz zostanie zapisany w folderze szkicu.
Jason C

@Jasonc dzięki za wskazówkę, byłem pewien, że musi być jakiś sposób, ale nie sądzę, żebym je edytować. Zostawiłem ramkę wokół zdjęć częściowo, aby je rozdzielić (2 pliki dla tak małych obrazów to przesada). Chcę zrobić kilka zdjęć w 512x512 (a szczególnie jeden mam pomysł), więc prześlę je w sposób sugerujesz.
Level River St

1
@steveverrill Haha, Warhols to miły akcent.
Jason C

@ Przetwarzanie Zom-B wydaje się robić wiele rzeczy, które (irytujące) nie zostały wspomniane w jego dokumentacji: nie wykorzystuje pełnych 256 logicznych wartości kanału kolorów w jego fizycznej wydajności, mieszając kolory, kiedy nie chcesz, używając pełnego rdzenia mój procesor nawet po zakończeniu rysowania. Nadal łatwo jest się w to zaangażować i możesz obejść te problemy, gdy wiesz, że one istnieją (z wyjątkiem pierwszego, jeszcze tego nie rozwiązałem ...)
Level River St

35

Właśnie ułożyłem wszystkie 16-bitowe kolory (5r, 6g, 5b) na krzywej Hilberta w JavaScript.

kolory krzywej Hilberta

Poprzednie zdjęcie (nie krzywa Hilberta):

krzywa Hilberta

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

Edycja : Okazuje się, że w mojej funkcji wystąpił błąd do obliczenia krzywej Hilberta i był niepoprawny; mianowicie r.x = (p.x & s) > 0; r.y = (p.y & s) > 0;zmieniono nar.x = (p.x & s) > 0 ? 1 : 0; r.y = (p.y & s) > 0 ? 1 : 0;

Edycja 2: Kolejny fraktal:

sierpinsky

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


Miły! Witamy w PPCG.
Jonathan Van Matre

Jak to wygląda, gdy przejście przez kolorowy sześcian jest również krzywą 3D Hilberta? Edytuj nm. ktoś to właśnie zrobił.
Mark Jeronimus

35

C #: Iteracyjna lokalna optymalizacja podobieństwa

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

Pomysł

Najpierw zaczynamy od losowego losowania:

wprowadź opis zdjęcia tutaj

Następnie losowo wybieramy dwa piksele i zamieniamy je. Jeśli to nie zwiększy podobieństwa pikseli do ich sąsiadów, zamieniamy się z powrotem i próbujemy ponownie. Powtarzamy ten proces w kółko.

Już po kilku pokoleniach (5000) różnice nie są tak oczywiste ...

wprowadź opis zdjęcia tutaj

Ale im dłużej działa (25000), ...

wprowadź opis zdjęcia tutaj

... tym bardziej pewne wzorce zaczynają się pojawiać (100000).

wprowadź opis zdjęcia tutaj

Używając różnych definicji sąsiedztwa , możemy wpływać na te wzorce i czy są one stabilne, czy nie. KernelJest macierzą podobne do tych używanych dla filtrów w przetwarzaniu obrazów . Określa wagi każdego sąsiada używanego do obliczania delty RGB.

Wyniki

Oto niektóre z utworzonych przeze mnie wyników. Filmy pokazują proces iteracyjny (1 klatka == 1000 pokoleń), ale niestety jakość nie jest najlepsza (vimeo, YouTube itp. Nie obsługują odpowiednio tak małych wymiarów). Mogę później spróbować stworzyć filmy lepszej jakości.

0 1 0
1 X 1
0 1 0

185000 pokoleń:

wprowadź opis zdjęcia tutaj Wideo (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 pokoleń:

wprowadź opis zdjęcia tutaj Wideo (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 pokoleń:

wprowadź opis zdjęcia tutaj Wideo (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

Jądro to jest interesujące, ponieważ ze względu na asymetrię wzory nie są stabilne, a cały obraz przesuwa się w prawo w miarę upływu pokoleń.

2331000 pokoleń:

wprowadź opis zdjęcia tutaj Wideo (01:10)


Duże wyniki (512 x 512)

Używanie powyższych jąder z większym wymiarem obrazu tworzy te same lokalne wzory, obejmując większy całkowity obszar. Obraz o wymiarach 512 x 512 potrzebuje od 1 do 2 milionów pokoleń do stabilizacji.

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj


OK, teraz bądźmy poważni i stwórzmy większe, mniej lokalne wzorce za pomocą radialnego jądra 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

To drastycznie wydłuża czas obliczeń na generację. 1,71 miliona pokoleń i 20 godzin później:

wprowadź opis zdjęcia tutaj


1
Dotarcie tam zajmuje trochę czasu, ale efekt końcowy jest całkiem niezły.
primo

Ciekawy przypadek, mam artykuł na ten sam temat: nayuki.io/page/simulated-annealing-demo
Nayuki

30

Jawa

Z kilkoma wariantami mojej drugiej odpowiedzi możemy uzyskać bardzo interesujące wyniki.

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

Ważny kod jest tutaj:

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

});

Wyjście (zrzut ekranu):

wprowadź opis zdjęcia tutaj

Zmień komparator na ten:

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

I otrzymujemy to:

wprowadź opis zdjęcia tutaj

Kolejna odmiana:

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

wprowadź opis zdjęcia tutaj

Jeszcze inna odmiana (przypomina mi automaty komórkowe):

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

wprowadź opis zdjęcia tutaj

Jeszcze inna odmiana (nowy osobisty faworyt):

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

wprowadź opis zdjęcia tutaj

Wygląda tak fraktalnie. XOR jest taki piękny, szczególnie zbliżenie:

wprowadź opis zdjęcia tutaj

Kolejne zbliżenie:

wprowadź opis zdjęcia tutaj

A teraz trójkąt Sierpińskiego przechylił się:

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

wprowadź opis zdjęcia tutaj


8
Pierwsze zdjęcie wygląda jak zdjęcie procesora lub pamięci
Nick T

@NickT Oto matryca pamięci (według Google Images) dla kontrastu: files.macbidouille.com/mbv2/news/news_05_10/25-nm-die.jpg
Justin

4
Racja, pamięć jest tak bezkształtna ... prawdopodobnie bardzo wielordzeniowy procesor: extremetech.com/wp-content/uploads/2012/07/Aubrey_Isle_die.jpg
Nick T

1
Naprawdę lubię te ostatnie. Bardzo efektownie wygląda, ale z podstawową strukturą organizacyjną. Chcę dywan tkany jak ten wzór XOR!
Jonathan Van Matre

To są naprawdę fajne; przypominają mi trochę zepsutą grę zręcznościową lub inną.
Jason C

29

Jawa

Nie byłem do końca pewien, jak utworzyć 15- lub 18-bitowe kolory, więc po prostu zostawiłem najmniej znaczący bit każdego kanału, aby stworzyć 2 ^ 18 różnych 24-bitowych kolorów. Większość hałasu jest usuwana przez sortowanie, ale skuteczne usuwanie hałasu wygląda tak, jakby wymagało porównania więcej niż tylko dwóch elementów jednocześnie, tak jak robi to Komparator. Spróbuję manipulować przy użyciu większych jąder, ale tymczasem jest to najlepsze, co mogłem zrobić.

wprowadź opis zdjęcia tutaj

Kliknij, aby wyświetlić obraz HD nr 2

Obraz o niskiej rozdzielczości # 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
Ten drugi naprawdę zasługuje na wersję
2496

Imgur przetwarza obraz od około pół godziny. Chyba próbuje to skompresować. W każdym razie dodałem link: SSend.it/hj4ovh
John P

2
Wystąpił problem z pobieraniem.
SuperJedi224,

28

Scala

Wszystkie kolory zamawiam, przechodząc trójwymiarową krzywą Hilberta przez system L. . Następnie przesuwam piksele na obrazie wyjściowym wzdłuż dwuwymiarowej krzywej Hilberta i układam wszystkie kolory.

Wyjście 512 x 512:

wprowadź opis zdjęcia tutaj

Oto kod. Większość z nich obejmuje logikę i matematykę poruszania się w trzech wymiarach za pomocą skoku / przechylenia / odchylenia. Jestem pewien, że był lepszy sposób na wykonanie tej części, ale no cóż.

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

DO#

Wow, naprawdę fajne rzeczy w tym wyzwaniu. Zrobiłem to w C # i wygenerowałem obraz 4096x4096 w około 3 minuty (procesor i7) przy użyciu każdego koloru za pomocą logiki Random Walk.

Ok, więc dla kodu. Po frustracji godzinami badań i próbach wygenerowania każdego koloru HSL za pomocą pętli w kodzie, zdecydowałem się na utworzenie płaskiego pliku do odczytu kolorów HSL. To, co zrobiłem, to utworzenie każdego koloru RGB na listę, a następnie uporządkowanie według odcienia, jasności, a następnie nasycenia. Następnie zapisałem listę do pliku tekstowego. ColorData to tylko mała klasa, którą napisałem, która akceptuje kolor RGB, a także przechowuje odpowiednik HSL. Ten kod to OGROMNY pożeracz pamięci RAM. Zużyto około 4 GB pamięci RAM lol.

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

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

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


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

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

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

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


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

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


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


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


}

Z tym na uboczu. Napisałem klasę, aby uzyskać następny kolor z wygenerowanego pliku. Pozwala ustawić początek i koniec odcienia. W rzeczywistości można to i prawdopodobnie uogólnić na dowolny wymiar, według którego plik został posortowany jako pierwszy. Zdaję sobie również sprawę, że w celu zwiększenia wydajności mogłem po prostu wstawić wartości RGB do pliku i utrzymać każdą linię na stałej długości. W ten sposób mogłem z łatwością określić przesunięcie bajtu zamiast zapętlania każdej linii, aż do linii, od której chciałem zacząć. Ale to nie był dla mnie hit. Ale oto ta klasa

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

Teraz, gdy mamy plik kolorów i mamy sposób na odczytanie pliku, możemy teraz zrobić obraz. Znalazłem klasę, aby zwiększyć wydajność ustawiania pikseli w bitmapie, zwaną LockBitmap. Źródło LockBitmap

Utworzyłem małą klasę Vector2 do przechowywania lokalizacji współrzędnych

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

Stworzyłem także klasę o nazwie SearchArea, która była pomocna w znajdowaniu sąsiednich pikseli. Podajesz piksel, dla którego chcesz znaleźć sąsiadów, granice, w których chcesz wyszukiwać, oraz rozmiar „kwadratu sąsiada”, który chcesz wyszukać. Jeśli więc rozmiar wynosi 3, oznacza to, że szukasz kwadratu 3x3 z określonym pikselem pośrodku.

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

}

Oto klasa, która faktycznie wybiera następnego sąsiada. Zasadniczo istnieją 2 tryby wyszukiwania. A) Pełny kwadrat, B) tylko obwód kwadratu. To była optymalizacja, której dokonałem, aby zapobiec przeszukiwaniu pełnego kwadratu ponownie po uświadomieniu sobie, że kwadrat jest pełny. DepthMap była kolejną optymalizacją, aby zapobiec przeszukiwaniu tych samych kwadratów w kółko. Jednak nie w pełni to zoptymalizowałem. Każde połączenie do GetNeighbors zawsze najpierw wykona pełne wyszukiwanie kwadratowe. Wiem, że mogłem to zoptymalizować, aby przeprowadzić wyszukiwanie po obwodzie tylko po wypełnieniu początkowego pełnego kwadratu. Po prostu nie doszedłem do tej optymalizacji i nawet bez niej kod jest dość szybki. Skomentowane linie „blokady” są dlatego, że użyłem Parallel.ForEach w pewnym momencie, ale zdałem sobie sprawę, że muszę napisać więcej kodu, niż chciałem dla tego 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;
    }
}

Ok świetnie, więc oto klasa, która tworzy obraz

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


}

A oto przykładowa implementacja:

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

    }
}

Jeśli RandomWalkSegments = 1, to po prostu zaczyna chodzić, gdziekolwiek chcesz, i zaczyna od pierwszego pierwszego koloru w pliku.

To nie jest najczystszy kod, który przyznaję, ale działa dość szybko!

Przycięte wyjście

3 ścieżki

128 ścieżek

EDYTOWAĆ:

Więc uczyłem się o OpenGL i Shaders. Wygenerowałem 4096x4096 przy użyciu każdego błyskawicznie płonącego koloru na GPU za pomocą 2 prostych skryptów cieniujących. Wynik jest nudny, ale pomyślałem, że ktoś może uznać to za interesujące i wymyślić kilka fajnych pomysłów:

Shader Vertex

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
}

Edycja (10/15/16): Chciałem tylko pokazać dowód koncepcji algorytmu genetycznego. Wciąż używam tego kodu 24 godziny później na zestawie losowych kolorów 100 x 100, ale jak dotąd wynik jest piękny!wprowadź opis zdjęcia tutaj

Edycja (26.10.2016): Już od 12 dni korzystam z kodu algorytmu genetycznego ... i wciąż optymalizuje wyniki. Zasadniczo jest zbliżony do jakiegoś lokalnego minimum, ale najwyraźniej wciąż znajduje większą poprawę:wprowadź opis zdjęcia tutaj

Edycja: 8/12/17 - Napisałem nowy algorytm chodzenia losowego - w zasadzie określasz liczbę „chodzików”, ale zamiast chodzić losowo - losowo wybiorą innego chodzika i albo go unikną (wybierz następny dostępny piksel najdalej ) - lub podejdź do nich (wybierz najbliższy dostępny piksel). Oto przykładowy wydruk w skali szarości (po zrobieniu kolorowania wykonam pełny rendering kolorów 4096x4096!):wprowadź opis zdjęcia tutaj


4
Trochę spóźniony, ale witamy w PPCG! To jest doskonały pierwszy post.
spaghetto

1
Dziękuję Ci! Nie mogę się doczekać ukończenia kolejnych wyzwań! Ostatnio robię więcej rzeczy związanych z kodowaniem obrazów, to moje nowe hobby
applejacks01

Wow, są niesamowite; Cieszę się, że wróciłem dziś do tego postu i sprawdziłem wszystkie późniejsze rzeczy.
Jason C

Dziękuję Ci! Właściwie robię teraz kodowanie algorytmem genetycznym, aby uzyskać ciekawe gradienty. Zasadniczo weź 10000 kolorów, tworząc siatkę 100 x 100. Dla każdego piksela uzyskaj sąsiednie piksele. Dla każdego uzyskaj odległość CIEDE2000. Podsumuj to. Zsumuj to dla wszystkich 10000 pikseli. Algorytm genetyczny próbuje zmniejszyć tę całkowitą sumę. Jest powolny, ale dla obrazu 20x20 jego wyjście jest naprawdę interesujące
applejacks01

Bardzo podoba mi się wynik tego rozwiązania.
r_alex_hall

22

Płótno HTML5 + JavaScript

Nazywam to randoGraph i możesz stworzyć tutaj tyle, ile chcesz

Kilka przykładów:

Przykład 1

przykład 2

przykład 3

przykład 4

przykład 5

przykład 6

przykład 7

Na przykład w przeglądarce Firefox można kliknąć obszar roboczy prawym przyciskiem myszy (po zakończeniu) i zapisać go jako obraz. Wytworzenie obrazu o wymiarach 4096 x 4096 stanowi pewien problem ze względu na limit pamięci niektórych przeglądarek.

Pomysł jest dość prosty, ale każdy obraz jest wyjątkowy. Najpierw tworzymy paletę kolorów. Następnie, zaczynając od punktów X, wybieramy losowe kolory z palety i ich pozycje (za każdym razem, gdy wybieramy kolor, usuwamy go z palety) i zapisujemy, gdzie nie umieszczamy go w tej samej pozycji następnego piksela.

Dla każdego stycznego piksela tworzymy liczbę (X) możliwych kolorów, a następnie wybieramy najbardziej odpowiedni dla tego piksela. Trwa to do momentu skompletowania obrazu.

Kod 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>

I JavaScript dla 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;
};

To miłe, ale wygląda na odpowiedź C # z fejesjoco . Czy to tylko przypadek?
AL

1
Algorytmy są tutaj i każdy może przeczytać i zrozumieć, że są naprawdę różne. Ta odpowiedź opublikowana po odpowiedzi C # z fejesjoco została ogłoszona zwycięzcą motywowaną tym, jak fajny jest jej wynik. Potem pomyślałem o zupełnie innym podejściu do przetwarzania i wybierania kolorów sąsiednich i to jest to. Oczywiście obie odpowiedzi mają takie same podstawy, takie jak równomierny rozkład kolorów zastosowanych w widmie widzialnym, koncepcja odpowiednich kolorów i punkty początkowe, być może przy tych podstawach ktoś mógłby pomyśleć, że produkowane obrazy są w niektórych przypadkach podobne.
konstantinosX

Okej, przepraszam, jeśli myślałeś, że krytykuję twoją odpowiedź. Zastanawiałem się, czy zainspirowała Cię odpowiedź fejesjoco, ponieważ wynikowe wyniki wyglądają podobnie.
AL

1
„Definiowanie metod klasy wewnątrz konstruktora zamiast korzystania z łańcucha prototypów jest naprawdę nieefektywne, szczególnie jeśli wspomniana klasa jest używana kilka razy.” To bardzo interesujący komentarz Patrick Roberts. Czy masz jakieś referencje z przykładem, który to potwierdza? , Szczerze chciałbym wiedzieć, czy to roszczenie ma jakąkolwiek podstawę (aby przestać go używać) i co to jest.
konstantinosX

2
Jeśli chodzi o użycie prototypu: działa on w taki sam sposób, jak metoda statyczna. Kiedy funkcja jest zdefiniowana w dosłownym znaczeniu obiektu, każdy nowy obiekt, który tworzysz, musi również utworzyć nową kopię funkcji i przechowywać je z tą instancją obiektu (więc 16 milionów obiektów kolorowych oznacza 16 milionów kopii tej samej funkcji w pamięć). Dla porównania użycie prototypu utworzy go tylko raz, który zostanie skojarzony z „klasą”, a nie z obiektem. Ma to oczywiste zalety pamięci, a także potencjalne korzyści prędkości.
Mwr247,

20

Pyton

Oto moje rozwiązanie w pythonie, jego utworzenie zajmuje prawie godzinę, więc prawdopodobnie jest pewna optymalizacja:

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

Oto kilka przykładowych wyników:

wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj


1
Wygląda jak szalone fale dźwiękowe
Mark Jeronimus

19

Jawa

Postanowiłem spróbować tego wyzwania. Zainspirowała mnie ta odpowiedź na kolejny kod golfowy. Mój program generuje brzydsze obrazy, ale mają wszystkie kolory.

Również mój pierwszy raz w golfa. :)

(4k zdjęć było zbyt duże jak na moją małą prędkość wysyłania, próbowałem przesłać jedno, ale po godzinie nie zostało przesłane. Możesz wygenerować własne.)

Zbliżenie:

Generuje obraz w 70 sekund na moim komputerze, zajmuje około 1,5 GB pamięci podczas generowania

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

Matematyka

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]

Wynik (2x):

256x128 2x

Oryginalny obraz 256 x 128

Edytować:

zastępując Log10 [i] Log10 [i] / 5, otrzymujesz: wprowadź opis zdjęcia tutaj

Powyższy kod dotyczy symulowanego wyżarzania. Tak widziany drugi obraz jest tworzony z wyższą „temperaturą” w pierwszych 10 ^ 6 krokach. Wyższa „temperatura” powoduje więcej permutacji między pikselami, podczas gdy na pierwszym obrazie struktura zamówionego obrazu jest nadal lekko widoczna.


17

JavaScript

Nadal jestem studentem i po raz pierwszy publikuję, więc moje kody prawdopodobnie są niechlujne i nie jestem w 100% pewien, że moje zdjęcia mają wszystkie potrzebne kolory, ale byłem bardzo zadowolony z wyników, więc pomyślałem, że je opublikuję.

Wiem, że konkurs się zakończył, ale bardzo podobały mi się ich wyniki i zawsze podobał mi się wygląd labiryntów generowanych rekurencyjnie, więc pomyślałem, że fajnie byłoby zobaczyć, jak wyglądałby, gdyby umieścił kolorowe piksele. Zaczynam więc od wygenerowania wszystkich kolorów w tablicy, a następnie wykonuję rekurencyjne cofanie, usuwając kolory z tablicy.

Oto mój 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);
    }
}

Obraz 256x128, kolory posortowane na czerwono-> zielono-> niebieski
Kolory sortowane RGB

Obraz 256x128, kolory posortowane na niebiesko-> zielono-> czerwony
BGR Sorted Colours

Obraz 256x128, kolory posortowane barwa-> luminancja-> nasycenie
HLS Sorted Colours

I wreszcie wygenerowany GIF jednego z nich
Color Labyrinth GIF


Twoje kolory są przycinane w najjaśniejszych regionach, co powoduje duplikaty. Zmień r * Math.ceil(255 / (colorSteps - 1)na r * Math.floor(255 / (colorSteps - 1), lub nawet lepiej: r * 255 / (colorSteps - 1)(niesprawdzone, ponieważ nie dostarczyłeś jsfiddle)
Mark Jeronimus

Ups, tak, miałem wrażenie, że będzie powodować problemy, mam nadzieję, że zostało to naprawione teraz i przepraszam za brak jsfiddle (nie wiedziałem, że istnieje!) Dzięki!
Kuligoawesome

Uwielbiam uporządkowane wyjście wyglądające na chaos / hałas tego i innego rozwiązania, które daje podobny efekt.
r_alex_hall

17

DO#

Zacząłem więc nad tym pracować jako zabawne ćwiczenie i skończyłem z efektem, który przynajmniej dla mnie wygląda całkiem schludnie. Kluczową różnicą w moim rozwiązaniu (przynajmniej) większości innych jest to, że generuję dokładnie tyle kolorów, ile potrzeba na początek i równomiernie rozprowadzam generację od czystej bieli do czystej czerni. Ustawiam także kolory pracujące w spirali wewnętrznej i wybieram następny kolor na podstawie średniej różnicy kolorów między wszystkimi ustawionymi sąsiadami.

Oto mała próbka, którą do tej pory stworzyłem, pracuję nad renderowaniem 4k, ale spodziewam się, że ukończenie zajmie dzień.

Oto próbka specyfikacji wyjściowej o wymiarach 256 x 128:

Spec Render

Niektóre większe obrazy z wciąż rozsądnymi czasami renderowania:

Renderuj przy 360 x 240

Druga seria przy 360 x 240 dała znacznie płynniejszy obraz

Renderuj # 2 przy 360 x 240

Po poprawieniu wydajności udało mi się uruchomić renderowanie HD, które zajęło 2 dni, nie zrezygnowałem jeszcze z 4K, ale może to potrwać tygodnie.

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

Jeśli ktoś ma jakieś przemyślenia na temat poprawy wydajności algorytmu wyboru kolorów, proszę dać mi znać, ponieważ w tej chwili renderowanie 360 ​​* 240 zajmuje około 15 minut. Nie sądzę, że można to zrównoważyć, ale zastanawiam się, czy byłby szybszy sposób na uzyskanie najniższej różnicy kolorów.
Phaeze

W jaki sposób obraz 360 * 240 stanowi „wszystkie kolory”? Jak produkujesz cbrt (360 * 240) = 44,208377983684639269357874002958 kolorów na komponent?
Mark Jeronimus,

Jaki to język? Losowe sortowanie listy i Random jest złym pomysłem, niezależnie od tego, ponieważ w zależności od algorytmu i implementacji może on powodować stronniczy wynik lub wyjątek stwierdzający, że "Comparison method violates its general contract!": ponieważ umowa tego stwierdza (x.compareTo(y)>0 && y.compareTo(z)>0) implies x.compareTo(z)>0. Aby randomizować listę, użyj dostarczonej metody losowania. ( colors.Shuffle()?)
Mark Jeronimus

@ MarkJeronimus Przyznaję, że przegapiłem specyfikację dotyczącą obrazu 256x128, zrobię proste rendery przy użyciu tych rozmiarów, koncentrowałem się na każdym pikselu, który jest unikalnym aspektem kolorystycznym wyzwania, a większe renderowanie, tak jak zrobiły to inne zgłoszenia.
Phaeze,

@MarkJeronimus Zdaję sobie sprawę, że losowy rodzaj jest zły, w rzeczywistości jest taki komentarz. To była tylko pozostałość po innym podejściu, które zacząłem i priorytetem było zrobienie dużych renderów, ponieważ zajmują one bardzo dużo czasu.
Phaeze,

16

Iść

Oto kolejny ode mnie, myślę, że daje to bardziej interesujące wyniki:

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

Zaczyna się od tego samego wzoru, co gif w mojej drugiej odpowiedzi . Następnie tasuje to w ten sposób:

tylko hałas

Im więcej iteracji uruchamiam raczej mało inspirujący algorytm porównywania sąsiadów, tym bardziej widoczny staje się wzór tęczy.

Oto 16384:

bardzo głośna tęcza w 16384 iteracjach

I 65536:

wprowadź opis zdjęcia tutaj


6
+1 Podoba mi się, że wyłania się z tego wzór; powinieneś zrobić z tego animację!
Jason C

16

Te obrazy to „Tęcza Langtona”. Są narysowane raczej po prostu: gdy mrówka Langtona się porusza, kolor jest rysowany na każdym pikselu przy pierwszej wizycie. Kolor do następnego rysowania jest następnie zwiększany o 1, zapewniając użycie 2 ^ 15 kolorów, po jednym dla każdego piksela.

EDYCJA: Stworzyłem wersję, która renderuje obrazy 4096 x 4096, używając 2 ^ 24 kolorów. Kolory są również „odbijane”, dzięki czemu tworzą ładne, gładkie gradienty. Podam tylko linki do nich, ponieważ są ogromne (> 28 MB)

Langton's Rainbow duża, rządzić LR

Langton's Rainbow duża, rządzić LLRR

// Koniec edycji.

To dla klasycznego zestawu reguł LR:

Langton's Rainbow LR

Oto LLRR:

Langton's Rainbow LLRR

Wreszcie ten wykorzystuje zestaw reguł LRRRRRLLR:

Langton's Rainbow LRRRRRLLR

Napisane w C ++, przy użyciu CImg do grafiki. Powinienem również wspomnieć o sposobie wyboru kolorów: po pierwsze używam niepodpisanego skrótu do przechowywania danych kolorów RGB. Za każdym razem, gdy komórka jest odwiedzana, przesuwam w prawo bity o jakąś wielokrotność 5, ORAZ 31, a następnie mnożę przez 8. Następnie niepodpisany krótki kolor jest zwiększany o 1. To daje wartości od 0 do 248 co najwyżej. Odejmuję jednak tę wartość od 255 w składnikach czerwonym i niebieskim, dlatego R i B są wielokrotnościami 8, zaczynając od 255, aż do 7:

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

Nie dotyczy to jednak zielonego składnika, który jest w wielokrotnościach 8 od 0 do 248. W każdym przypadku każdy piksel powinien zawierać unikalny kolor.

W każdym razie kod źródłowy jest poniżej:

#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
Witamy i dołącz do klubu. Może warto wypróbować inne turmity z code.google.com/p/ruletablerepository/wiki/… (uczestniczyłem w tym)
Mark Jeronimus

3
Linki do obrazów nie działają, ponieważ Dropbox zabił foldery publiczne.
user2428118 27.04.17

15

Rubin

Zdecydowałem, że pójdę dalej i zrobię PNG od zera, ponieważ pomyślałem, że to będzie interesujące. Ten kod dosłownie wyprowadza surowe dane binarne do pliku.

Zrobiłem wersję 512x512. (Algorytm jest jednak raczej nieciekawy.) Na moim komputerze kończy się za około 3 sekundy.

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'

Wyjście (w all_colors.png) (kliknij dowolny z tych obrazów, aby je powiększyć):

Wynik

Nieco bardziej interesujące wyjście gradientowe (poprzez zmianę czwartego na ostatni wiersz na }.shuffle }):

Wyjście 2

A zmieniając go na }.shuffle }.shuffle, otrzymujesz szalone linie kolorów:

Wyjście 3


To jest naprawdę fajne. Czy istnieje sposób, aby uczynić to ładniejszym? Może randomizować piksele? Scoring is by vote. Vote for the most beautiful images made by the most elegant code.

1
@LowerClassOverflowian Ok, edytowane
Klamka

Dużo lepiej!!!!!!!

1
Co się stanie, jeśli zmienisz czwartą na ostatnią linię na }.shuffle }.shuffle }.shuffle?
John Odom

6
@John Erm, prawdopodobnie błąd składniowy?
Klamka

14

Pyton

osocze

Używanie pytona do sortowania kolorów według luminancji, generowania wzoru luminancji i wybierania najbardziej odpowiedniego koloru. Piksele są iterowane w kolejności losowej, dzięki czemu mniej korzystne dopasowania luminancji, które występują naturalnie, gdy lista dostępnych kolorów jest mniejsza, są równomiernie rozmieszczone na całym obrazie.

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

Jawa

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

Wybrałem 4096 na 4096, ponieważ nie mogłem wymyślić, jak uzyskać wszystkie kolory bez tego.

Wynik:

Za duży, by zmieścić się tutaj. To jest zrzut ekranu:

wprowadź opis zdjęcia tutaj

Z niewielką zmianą możemy uzyskać piękniejsze zdjęcie:

Dodaj Collections.shuffle(points, new Random(0));między generowaniem punktów a robieniem kolorów:

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

wprowadź opis zdjęcia tutaj

Zbliżenie:

wprowadź opis zdjęcia tutaj


29
Nazywasz dużą szarą kroplę „piękną”?
Klamka

22
@Doorknob Tak. Nazywam to bardzo pięknym. To niesamowite, że wszystkie kolory można ułożyć w dużą szarą kroplę. Blob wydaje mi się bardziej interesujący, kiedy powiększam. Przy odrobinie więcej szczegółów możesz zobaczyć, jak nieprzypadkowy jest rng Java. Kiedy powiększamy jeszcze bardziej, jak na drugim zrzucie ekranu, staje się jasne, ile kolorów w tym jest. Kiedy powiększam jeszcze bardziej, wygląda to na program Piet.
Justin

Dostałem kolory w mniejszych wersjach, upuszczając niższe bity.
Mark Jeronimus

Tak, dolne bity r, ga bosobno, ale miałem do czynienia z nimi jako jeden numer.
Justin

Widzę, że wymyśliłeś sobie magicz w następnej odpowiedzi. Na ten temat interesujące może być eksperymentowanie z własną Randompodklasą, która generuje jeszcze mniej idealne liczby losowe.
Mark Jeronimus

13

C ++ 11

( Aktualizacja: dopiero później zauważyłem, że wypróbowano już podobne podejście - z większą cierpliwością w odniesieniu do liczby iteracji).

Dla każdego piksela definiuję zestaw sąsiednich pikseli. Rozbieżność między dwoma pikselami definiuję jako sumę kwadratów ich różnic R / G / B. Kara danego piksela jest wówczas sumą rozbieżności między pikselem a jego sąsiadami.

Teraz najpierw generuję losową permutację, a następnie zaczynam wybierać losowe pary pikseli. Jeśli zamiana dwóch pikseli zmniejsza sumę całkowitych kar wszystkich pikseli, zamiana przechodzi. Powtarzam to milion razy.

Dane wyjściowe są w formacie PPM, które przekonwertowałem do formatu PNG przy użyciu standardowych narzędzi.

Źródło:

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

Różnicowanie stopnia sąsiadów daje różne wyniki. Można to poprawić w funkcji Image :: pixelNeighbors (). Kod zawiera przykłady czterech opcji: (patrz źródło)

przykład 01 przykład 02 przykład 03 przykład 04

Edycja: kolejny przykład podobny do czwartego powyżej, ale z większym jądrem i większą liczbą iteracji:

przykład 05

Jeszcze jedno: za pomocą

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

i dziesięć milionów iteracji, mam to:

przykład 06


11

Nie jest to najbardziej elegancki kod, ale interesujący z dwóch powodów: obliczanie liczby kolorów na podstawie wymiarów (o ile iloczyn wymiarów jest potęgą dwóch) i wykonywanie potrójnej przestrzeni kolorów:

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

Kilka interesujących odmian można uzyskać, zmieniając klauzulę OrderBy:

x:

wprowadź opis zdjęcia tutaj

y:

wprowadź opis zdjęcia tutaj

z:

wprowadź opis zdjęcia tutaj

Y:

wprowadź opis zdjęcia tutaj

Chciałbym dowiedzieć się, co spowodowało nieparzyste linie w pierwszych trzech


2
Te nieparzyste wiersze są prawdopodobnie uprzedzeniem jakiejś metody lub metody wyszukiwania (wyszukiwanie binarne / szybkie sortowanie?)
Mark Jeronimus

Naprawdę bardzo lubię te linie.
Jason C

11

Jawa

To był o wiele lepszy pomysł. To jest bardzo krótki kod Java; główna metoda ma tylko 13 linii:

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

Generuje bloki „selektorów kolorów”. Zasadniczo w pierwszym bloku, r=0w drugim r=1itd. W każdym bloku gprzyrosty względem xi bwzględemy .

Naprawdę lubię bitowe operatory. Pozwól mi rozbić setRGBoświadczenie:

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

W wyniku działania operatorów bitowych zajmuje to tylko 7 sekund. Jeśli r & 15zostanie zastąpiony r % 16, zajmuje to 9 sekund.

Wybrałem 4096 x 4096

Wyjście (zrzut ekranu, w przeciwnym razie zbyt duży):

wprowadź opis zdjęcia tutaj

Wyjście ze złym uśmiechem narysowanym na nim przez odręczne czerwone kółka:

wprowadź opis zdjęcia tutaj


2
Link do oryginału, dzięki czemu mogę zweryfikować poprawność (policzyć kolory)
Mark Jeronimus

2
Lol! Zapomniałem, że mogę uruchomić kod Java. Pierwszy obraz mija i nie mogę odtworzyć drugiego obrazu (lol) ☺
Mark Jeronimus

16
Wszystkie koła odręczne mają ten sam kolor, zdyskwalifikowany. : P
Nick T

3
@Quincunx +1, jeśli możesz narysować przerażającą twarz i nadal zachować wymagania dotyczące kolorów!
Jason C

2
@JasonC Zobacz moją odpowiedź. Podziękowania dla Quincunx za inspirację.
Level River St
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.