Solo rachado gerado por computador

43

Escreva um programa que inclua um número inteiro de 0 a 65535 (2 16 -1) e gere uma imagem única de 500 × 500 pixels que seja o mais semelhante possível a essas 6 imagens da vida real de solo rachado:

amostra de solo rachado 1 amostra de solo rachado 2 amostra de solo rachado 3 amostra de solo rachado 4 amostra de solo rachado 5 amostra de solo rachado 6
Essas são miniaturas, clique nelas para ver as imagens em tamanho real de 500 × 500.

O objetivo aqui é tornar as imagens geradas por computador o mais realistas possível . Idealmente, se qualquer uma das imagens geradas pelo seu programa fosse misturada com as 6 imagens acima, alguém vendo as imagens pela primeira vez não seria capaz de diferenciar as geradas por computador das reais.

O fotorrealismo perfeito é complicado, no entanto, faça o melhor que puder. Este é um para que as respostas com resultados mais realistas sejam mais votadas e mais propensas a vencer.

Regras

  • Você pode usar funções ou bibliotecas de processamento de imagem.

  • Você pode basear seu algoritmo nas informações coletadas das 6 imagens de amostra, mas suas possíveis imagens de saída 65536 (2 16 ) devem ser visualmente distintas uma da outra e das imagens de amostra, especialmente no que diz respeito à disposição das rachaduras. Você deve realmente gerar suas imagens, não apenas girar e traduzir uma seleção de uma foto preexistente.

  • Caso contrário, você não deve codificar suas saídas. Um algoritmo genérico deve ser usado e números maiores que 65535 devem, teoricamente, produzir saídas válidas. (Eu o restringi apenas para acomodar tipos inteiros com um máximo pequeno).

  • O número inteiro de entrada pode ser pensado como uma semente que resulta em uma imagem aleatória de saída do solo rachado. Porém, deve ser determinístico, portanto a mesma entrada sempre deve resultar na mesma saída.

  • As imagens de saída devem ter exatamente 500 × 500 pixels.

  • As imagens de saída podem ser salvas em qualquer formato de arquivo de imagem comum ou simplesmente exibidas.

  • Certifique-se de incluir alguns exemplos de imagens de saída em sua resposta e os números de entrada correspondentes.

  • A resposta com mais votos vence. É claro que os eleitores devem votar de forma positiva as respostas que tentam produzir imagens semelhantes às 6 amostras, e as de votos negativos que violam regras ou dão resultados inconsistentes.

As 6 imagens de amostra foram tiradas em texturelib.com . As seleções de área de 1000 × 1000 pixels foram obtidas de duas imagens maiores de solo rachado e redimensionadas para 500 × 500. Você pode usar a análise dessas imagens maiores em seu programa, mas a saída deve imitar especificamente as 6 imagens de amostra escolhidas.

Hobbies de Calvin
fonte
6
Votei em encerrar este desafio como Muito Amplo, porque não possui critérios objetivos de validade .
AdmBorkBork
4
@HelkaHomba Se um desafio antigo foi bem recebido ou não, não deveria ter influência se um desafio agora se encaixa nas regras do site, conforme decidido por consenso. Os PopCons tiveram uma tremenda discussão nos últimos meses, um dos resultados foi que todos os PopCons precisam de um critério de validade objetivo. Este desafio não tem isso. Portanto, é muito amplo.
AdmBorkBork
15
As regras atuais sobre os contras do pop são tão estúpidas que eu aproveitaria a chance para ignorá-las e ver como as coisas funcionam. Este tópico foi abordado na meta, onde é discutido até a morte, mas nada realmente muda, então acho que a melhor chance de algo acontecer é manter alguns contras pop e ver como eles acontecem.
xnor
6
Os critérios de validade objetivo aqui são "exclusivos" (distintos dos outros 65535) e "500x500 pixels". A semelhança com as imagens de exemplo não pode ser definida objetivamente, ou isso não seria um concurso de popularidade, mas um desafio de código.
Trichoplax
14
Vejo maus pop como "fazer algo bonito" sem restrição, e bons pop como "corresponder a esta especificação" com humanos votando em qual combina melhor. Definitivamente, vejo esse desafio como bom.
Trichoplax

Respostas:

30

Mathematica

Um diagrama de Voronoi se parece com este desenho, da Wikipedia, mostrando 19 células, cada uma contendo um único ponto inicial. Uma célula consiste na sub-região de pontos aos quais o respectivo ponto de geração está mais próximo do que qualquer outro ponto de semente.

voronoi

O código abaixo gera um diagrama a partir de 80 pontos aleatórios (na região quadrada delimitada por (-1, -1) e (1,1)).

Ele usa as primitivas de polígono (em 2D) no diagrama para criar poliedros (em 3D). Imagine que cada polígono tem, logo abaixo, uma tradução (-.08 em z) de si mesma. Pense nos dois polígonos como a face superior e inferior de um poliedro. "Faces laterais" são então adicionadas para completar o poliedro.

Cada poliedro é então traduzido para fora, a partir do centro da imagem, no plano xy; afasta-se do meio. A magnitude da tradução varia diretamente com a distância entre o ponto aleatório de geração original do poliedro e o centro da tela. Essa "expansão" do poliedro no plano xy resulta em fendas.

crackedMud[1]

1

crackedMud[65535]

último

Código

ClearAll[polyhedronFromPolygon, voronoiPolygons, generatingPointFromPolygon, crackedMud]


(* polyhedronFromPolygon returns a single polyhedron from a polygon *)

polyhedronFromPolygon[polygon_] :=      
 Module[{twoPolygons, verticesOfUpperPolygonCell, nVertices, n = 1},
 verticesOfUpperPolygonCell = Join @@ (polygon[[1]] /. {x_, y_} :> {{x, y, 0}, {x, y, -.08}});
 (* number of vertices in a single *Voronoi* cell *)
 nVertices = Length[verticesOfUpperPolygonCell]/2;   

(*vertex indices of the upper and lower polygon faces *)  
twoPolygons = Select[Range@(2*nVertices), #] & /@ {OddQ, EvenQ};    

(*vertex indices of a rectangular face of the polyhedron *)
While[n < nVertices + 1, AppendTo[twoPolygons,
    {twoPolygons[[1, n]], twoPolygons[[2, n]], 
     twoPolygons[[2, If[n + 1 < nVertices + 1, n + 1, 1]]], 
     twoPolygons[[1, If[n + 1 < nVertices + 1, n + 1, 1]]]}]; n++];
(*the graphics complex returned is a polyhedron, even though it says Polygon *)
 GraphicsComplex[verticesOfUpperPolygonCell, Polygon[twoPolygons]] ] 


(* takes two dimensional coordinates and returns all of the cells of a Voronoi diagram *)

voronoiPolygons[pts_] := 
Module[{voronoiRegion, data},
  voronoiRegion = VoronoiMesh[pts, ImageSize -> Medium, 
  PlotTheme -> "Lines", Axes -> True, AxesOrigin -> {0, 0}];
  data = Join @@ (MeshPrimitives[voronoiRegion, 2][[All, 1]] /. {x_, y_} :> {{x, y, 0}, {x, y, .04}});
 (* the mesh primitives are the polygons *)
  MeshPrimitives[voronoiRegion, 2]]   

(* Returns, in 3D, the point which was used to generate the nth Voronoi cell. *)
generatingPointFromPolygon[n_, points_, pgons_] := 
 FirstCase[points, {x_, y_} /; RegionMember[pgons[[n]], {x, y}] :> {x,y,0}]

crackedMud[seedNumber_] :- 
 Module[{pts, pts3D, geometricImage, nPts, polygons, polyhedra, centerPtinImage},
  SeedRandom[seedNumber];
  nPts = 80;
  pts = RandomReal[{-1, 1}, {nPts, 2}];
  pts3D = pts /. {x_, y_} :> {x, y, .0};
  polygons = voronoiPolygons[pts];
  polyhedra = polyhedronFromPolygon /@ polygons;
  centerPtinImage =   (Mean /@ (PlotRange /. 
        AbsoluteOptions[
         Graphics3D[{polyhedra, Blue, Point@pts3D}, Axes -> False, 
         Boxed -> False]])) /. {x_Real, y_, _} :> {x, y, 0};
  geometricImage =
  Graphics3D[{RGBColor[0.75, 0.75, 0.8], EdgeForm[Darker@Gray],
        (* # is the nth polygon which yields the nth polyhedron *)
        (* generatingPointFromPolygon returns the point the generated the #th polygon *)

     GeometricTransformation[{polyhedronFromPolygon[polygons[[#]]]},   
        TranslationTransform[(generatingPointFromPolygon[#, pts, polygons] - centerPtinImage)/5]] & /@ Range@nPts},
         Axes -> False,  Boxed -> False, ViewPoint -> {0., -1, 1.5}, 
         Background -> Black, ImageSize -> 1200];

     (*ImageTrim returns a 500 by 500 pixel clip from the center of the image *)
     ImageTrim[
        (*ImageEffect speckles the image *)
        ImageEffect[Rasterize[geometricImage], {"Noise", 1/5}], 
     {{250, 250}, {750, 750}}]
  ] 
DavidC
fonte
Convém adaptar-se a isso para um criador de padrões de vidro quebrado.
Sparr
@ Sparr, sim, parece vidro quebrado (ou ladrilhos).
DavidC
Golfed ........?
gato
@cat Não, não é jogado golfe.
DavidC
@DavidC Onde está todo o espaço em branco? Você escreve assim? O Wolfram aplica código ilegível?
cat
24

Java

Eu usei uma abordagem baseada em diagramas recursivos de Voronoi. As saídas não parecem muito realistas, mas acho que estão bem.

Aqui estão alguns exemplos de imagens (redimensionadas para 250x250 para não preencher a tela inteira):

0:

Imagem 0

1:

Imagem 1

Mais detalhes sobre o algoritmo:

Todas as imagens nesta seção estão usando a mesma semente.

O algoritmo começa gerando um diagrama de Voronoi com 5 pontos:

Diagrama de Voronoi

Se olharmos para as imagens originais no desafio, podemos ver que as linhas não são todas retas assim, então pesamos a distância por um valor aleatório, com base no ângulo do ponto, além disso, ângulos mais próximos fornecem valores mais próximos :

Diagrama de Voronoi ponderado

Agora, desenhamos recursivamente esses tipos de diagramas de Voronoi dentro de cada região, com linhas mais finas e transparentes, e removemos o fundo, com uma profundidade de recursão máxima de 3, e obtemos:

Voronoi recursivo

Agora, basta adicionar o fundo marrom claro e pronto!

Feito!

Código:

O código consiste em três classes Main.java, VoronoiPoint.javae Vector.java:

Main.java:

import java.awt.Desktop;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;

import javax.imageio.ImageIO;

public class Main {
    public static int WIDTH = 500;
    public static int HEIGHT = 500;
    public static int RECURSION_LEVELS = 3;
    public static int AMOUNT_OF_POINTS = 5;
    public static int ROTATION_RESOLUTION = 600;
    public static int ROTATION_SMOOTHNESS = 10;
    public static int BACKGROUND = 0xFFE0CBAD;

    public static Random RAND;

    public static void main(String[] args) {

        int seed = new Random().nextInt(65536);
        if (args.length == 1) {
            System.out.println(Arrays.toString(args));
            seed = Integer.parseInt(args[0]);
        } else {
            System.out.println("Generated seed: " + seed);
        }
        RAND = new Random(seed);

        ArrayList<Vector> points = new ArrayList<Vector>();
        for (int x = 0; x < WIDTH; x++) {
            for (int y = 0; y < HEIGHT; y++) {
                points.add(new Vector(x, y));
            }
        }
        BufferedImage soil = generateSoil(WIDTH, HEIGHT, seed, points, AMOUNT_OF_POINTS, RECURSION_LEVELS);

        BufferedImage background = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        for (int x = 0; x < background.getWidth(); x++) {
            for (int y = 0; y < background.getHeight(); y++) {
                background.setRGB(x, y, BACKGROUND ^ (RAND.nextInt(10) * 0x010101));
            }
        }

        Graphics g = background.getGraphics();
        g.drawImage(soil, 0, 0, null);
        g.dispose();

        String fileName = "soil";
        File output = new File(fileName + ".png");
        for (int i = 0; output.exists(); i++) {
            output = new File(fileName + i + ".png");
        }
        try {
            ImageIO.write(background, "png", output);
            Desktop.getDesktop().open(output);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("Done. Saved as " + output);
    }

    private static BufferedImage generateSoil(int width, int height, int seed, ArrayList<Vector> drawPoints,
            int amountOfPoints, int recursionLevel) {

        BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

        ArrayList<VoronoiPoint> points = new ArrayList<VoronoiPoint>();
        for (int i = 0; i < amountOfPoints; i++) {
            points.add(new VoronoiPoint(drawPoints.get(RAND.nextInt(drawPoints.size()))));
        }

        HashMap<Integer, ArrayList<Vector>> pointMaps = new HashMap<Integer, ArrayList<Vector>>();
        for (VoronoiPoint point : points) {
            pointMaps.put(point.hashCode(), new ArrayList<Vector>());
        }
        System.out.println(pointMaps);

        System.out.println(points);

        for (Vector v : drawPoints) {
            VoronoiPoint closest = null;
            VoronoiPoint secondClosest = null;

            for (VoronoiPoint point : points) {
                double distance = point.getMultiplicativeDistanceTo(v);
                if (closest == null || distance < closest.getMultiplicativeDistanceTo(v)) {
                    secondClosest = closest;
                    closest = point;
                } else if (secondClosest == null || distance < secondClosest.getMultiplicativeDistanceTo(v)) {
                    secondClosest = point;
                }
            }

            int col = 0;
            if (Math.abs(closest.getMultiplicativeDistanceTo(v)
                    - secondClosest.getMultiplicativeDistanceTo(v)) < (recursionLevel * 5 / RECURSION_LEVELS)) {
                col = 0x01000000 * (recursionLevel * 255 / RECURSION_LEVELS);
            } else {
                pointMaps.get(closest.hashCode()).add(v);
            }
            result.setRGB((int) v.getX(), (int) v.getY(), col);
        }
        Graphics g = result.getGraphics();
        if (recursionLevel > 0) {
            for (ArrayList<Vector> pixels : pointMaps.values()) {
                if (pixels.size() > 10) {
                    BufferedImage img = generateSoil(width, height, seed, pixels, amountOfPoints,
                            recursionLevel - 1);
                    g.drawImage(img, 0, 0, null);
                }
            }
        }
        g.dispose();

        return result;
    }

    public static int modInts(int a, int b) {
        return (int) mod(a, b);
    }

    public static double mod(double a, double b) {
        a = a % b;
        while (a < 0)
            a += b;
        return a;
    }
}

VoronoiPoint.java:

public class VoronoiPoint {

    private Vector pos;
    private double[] distances;

    public VoronoiPoint(Vector pos) {
        this.pos = pos;
        distances = new double[Main.ROTATION_RESOLUTION];
        for (int i = 0; i < distances.length; i++)
            distances[i] = Main.RAND.nextFloat() / 2 + 0.51;

        for (int iter = 0; iter < Main.ROTATION_SMOOTHNESS; iter++) {
            for (int i = 0; i < distances.length; i++) {
                distances[i] = (distances[Main.modInts(i - Main.RAND.nextInt(4) - 2, distances.length)] + distances[i]
                        + distances[Main.modInts(i + Main.RAND.nextInt(4) - 2, distances.length)]) / 3;
            }
        }
    }

    public Vector getPos() {
        return pos;
    }

    public double getRotationFromAngle(double radians) {
        return distances[(int) (Main.mod(Math.toDegrees(radians) / 360, 1) * distances.length)];
    }

    public double getRotationFromVector(Vector vec) {
        return getRotationFromAngle(Math.atan2(pos.getY() - vec.getY(), -(pos.getX() - vec.getX())));
    }

    public double getMultiplicativeDistanceTo(Vector other) {
        return pos.getLengthTo(other) * getRotationFromVector(other);
    }

    public String toString() {
        return "VoronoiPoint(pos=[" + pos.getX() + ", " + pos.getY() + "])";
    }

    public int hashCode() {
        return distances.hashCode() ^ pos.hashCode();
    }
}

Vector.java: (Esta classe é copiada de um dos meus outros projetos, portanto, contém algum código desnecessário)

package com.loovjo.soil;

import java.util.ArrayList;
import java.util.Random;

public class Vector {
    private static final float SMALL = 1f / Float.MAX_EXPONENT * 100;
    private float x, y;

    public Vector(float x, float y) {
        this.setX(x);
        this.setY(y);
    }

    public Vector(int x, int y) {
        this.setX(x);
        this.setY(y);
    }

    public Vector(double x, double y) {
        this.setX((float) x);
        this.setY((float) y);
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    /*
     * Gets the length ^ 2 This is faster than getting the length.
     */
    public float getLengthToSqrd(float x, float y) {
        return (float) ((this.x - x) * (this.x - x) + (this.y - y) * (this.y - y));
    }

    public float getLengthToSqrd(Vector v) {
        return getLengthToSqrd(v.x, v.y);
    }

    public float getLengthSqrd() {
        return getLengthToSqrd(0, 0);
    }

    public float getLengthTo(float x, float y) {
        return (float) Math.sqrt(getLengthToSqrd(x, y));
    }

    public float getLengthTo(Vector v) {
        return getLengthTo(v.x, v.y);
    }

    public float getLength() {
        return getLengthTo(0, 0);
    }

    public Vector setLength(float setLength) {
        float length = getLength();
        x *= setLength / length;
        y *= setLength / length;
        return this;
    }

    public float getFastLengthTo(float x, float y) {
        return getFastLengthTo(new Vector(x, y));
    }

    public float getFastLengthTo(Vector v) {
        float taxiLength = getTaxiCabLengthTo(v);
        float chebyDist = getChebyshevDistanceTo(v);
        return Float.min(taxiLength * 0.7f, chebyDist);
    }

    public float getFastLength() {
        return getLengthTo(0, 0);
    }

    public Vector setFastLength(float setLength) {
        float length = getFastLength();
        x *= setLength / length;
        y *= setLength / length;
        return this;
    }

    public float getTaxiCabLengthTo(float x, float y) {
        return Math.abs(this.x - x) + Math.abs(this.y - y);
    }

    public float getTaxiCabLengthTo(Vector v) {
        return getTaxiCabLengthTo(v.x, v.y);
    }

    public float getTaxiCabLength() {
        return getTaxiCabLengthTo(0, 0);
    }

    public Vector setTaxiCabLength(float setLength) {
        float length = getTaxiCabLength();
        x *= setLength / length;
        y *= setLength / length;
        return this;
    }

    public Vector absIfBoth() {
        if (x < 0 && y < 0)
            return new Vector(-x, -y);
        return this;
    }

    public Vector abs() {
        return new Vector(x < 0 ? -x : x, y < 0 ? -y : y);
    }

    public float getChebyshevDistanceTo(float x, float y) {
        return Math.max(Math.abs(this.x - x), Math.abs(this.y - y));
    }

    public float getChebyshevDistanceTo(Vector v) {
        return getChebyshevDistanceTo(v.x, v.y);
    }

    public float getChebyshevDistance() {
        return getChebyshevDistanceTo(0, 0);
    }

    public Vector setChebyshevLength(float setLength) {
        float length = getChebyshevDistance();
        x *= setLength / length;
        y *= setLength / length;
        return this;
    }

    public Vector sub(Vector v) {
        return new Vector(this.x - v.getX(), this.y - v.getY());
    }

    public Vector add(Vector v) {
        return new Vector(this.x + v.getX(), this.y + v.getY());
    }

    public Vector mul(Vector v) {
        return new Vector(this.x * v.getX(), this.y * v.getY());
    }

    public Vector mul(float f) {
        return mul(new Vector(f, f));
    }

    public Vector div(Vector v) {
        return new Vector(this.x / v.getX(), this.y / v.getY());
    }

    public Vector div(float f) {
        return div(new Vector(f, f));
    }

    public Vector mod(Vector v) {
        return new Vector(this.x % v.getX(), this.y % v.getY());
    }

    public Vector mod(int a, int b) {
        return mod(new Vector(a, b));
    }

    public Vector mod(int a) {
        return mod(a, a);
    }

    public String toString() {
        return "Vector(" + getX() + ", " + getY() + ")";
    }

    /*
     * Returns a list with vectors, starting with this, ending with to, and each
     * one having length between them
     */
    public ArrayList<Vector> loop(Vector to, float length) {
        Vector delta = this.sub(to);
        float l = delta.getLength();
        ArrayList<Vector> loops = new ArrayList<Vector>();
        for (float i = length; i < l; i += length) {
            delta.setLength(i);
            loops.add(delta.add(to));
        }
        loops.add(this);

        return loops;
    }

    public boolean intersects(Vector pos, Vector size) {
        pos.sub(this);
        if (pos.getX() < getX())
            return false;
        if (pos.getY() < getY())
            return false;
        return true;
    }

    public Vector copy() {
        return new Vector(x, y);
    }

    public void distort(float d) {
        x += Math.random() * d - d / 2;
        y += Math.random() * d - d / 2;
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof Vector) {
            Vector v = (Vector) o;
            return getLengthToSquared(v) < SMALL * SMALL;
        }
        return false;
    }

    private float getLengthToSquared(Vector v) {
        return sub(v).getLengthSquared();
    }

    private float getLengthSquared() {
        return x * x + y * y;
    }

    public boolean kindaEquals(Vector o, int i) {
        if (o.x + i < x)
            return false;
        if (o.x - i > x)
            return false;
        if (o.y + i < y)
            return false;
        if (o.y - i > y)
            return false;
        return true;
    }
    /*
     * Gets the direction, from 0 to 8.
     */
    public int getDirection() {
        return (getDirectionInDegrees()) / (360 / 8);
    }
    /*
     * Gets the direction in degrees.
     */
    public int getDirectionInDegrees() {
        return (int) positize((float) Math.toDegrees(Math.atan2(x, -y)), 360f);
    }

    private float positize(float f, float base) {
        while (f < 0)
            f += base;
        return f;
    }
    // 0 = north,
            // 1 = northeast,
            // 2 = east,
            // 3 = southeast,
            // 4 = south,
            // 5 = southwest,
            // 6 = west,
            // 7 = northwest
    public Vector moveInDir(int d) {
        d = d % 8;
        d = (int) positize(d, 8);

        if (d == 0)
            return this.add(new Vector(0, -1));
        if (d == 1)
            return this.add(new Vector(1, -1));
        if (d == 2)
            return this.add(new Vector(1, 0));
        if (d == 3)
            return this.add(new Vector(1, 1));
        if (d == 4)
            return this.add(new Vector(0, 1));
        if (d == 5)
            return this.add(new Vector(-1, 1));
        if (d == 6)
            return this.add(new Vector(-1, 0));
        if (d == 7)
            return this.add(new Vector(-1, -1));
        return this;
    }
    /*
     * Gets the angle in degrees to o.
     */
    public float getRotationTo(Vector o) {
        float d = (float) Math.toDegrees((Math.atan2(y - o.y, -(x - o.x))));
        while (d < 0)
            d += 360;
        while (d > 360)
            d -= 360;
        return d;
    }
    public float getRotation() {
        return getRotationTo(new Vector(0, 0));
    }
    /*
     * In degrees
     */
    public Vector rotate(double n) {
        n = Math.toRadians(n);
        float rx = (float) ((this.x * Math.cos(n)) - (this.y * Math.sin(n)));
        float ry = (float) ((this.x * Math.sin(n)) + (this.y * Math.cos(n)));
        return new Vector(rx, ry);
    }

    public int hashCode() {
        int xx = (int) x ^ (int)(x * Integer.MAX_VALUE);
        int yy = (int) y ^ (int)(y * Integer.MAX_VALUE);
        return new Random(12665 * xx).nextInt() ^ new Random(5349 * yy).nextInt() + new Random((30513 * xx) ^ (19972 * yy)).nextInt();
    }

    public boolean isPositive() {
        return x >= 0 && y >= 0;
    }

    public Vector clone() {
        return new Vector(x, y);
    }
}

Mas não quero compilar um monte de classes Java!

Aqui está um arquivo JAR que você pode executar para gerar essas imagens você mesmo. Corra como java -jar Soil.jar number, onde numberestá a semente (pode ser qualquer coisa até 2 31 -1), ou corra como java -jar Soil.jar, e escolhe uma semente por si só. Haverá alguma saída de depuração.

Loovjo
fonte
Por alguma razão, acho essas imagens bastante realistas e também completamente falsas. A falta de sombras naturais está me excitando.
Fatalize
Se ajudar, você pode fazer upload de imagens em tamanho real e fazer pequenas miniaturas, como no post de desafio, ou imagens médias que se encaixam em 2 e ocupam menos espaço vertical. Na fonte do desafio, você pode ver como adicionar um "s" no endereço imgur torna as imagens pequenas e também é possível usar um "m" para mídia. A fonte também mostra como transformar a imagem pequena em um link para a imagem em tamanho real.
Trichoplax
2
Eu acho que as cores podem estar muito mais próximas - mais cinza, menos bege. Mas de outra maneira boa resposta!
Passatempos de Calvin
12

Python 3 (usando a biblioteca Kivy e GLSL)

Primeira imagem gerada

insira a descrição da imagem aqui

Código Python:

import os
os.environ['KIVY_NO_ARGS'] = '1'

from kivy.config import Config
Config.set('input','mouse','mouse,disable_multitouch')
Config.set('graphics', 'width', '500')
Config.set('graphics', 'height', '500')
Config.set('graphics', 'resizable', '0')
Config.set('graphics', 'borderless', '1')
Config.set('graphics', 'fbo', 'force-hardware')

from kivy.app import App
from kivy.graphics import RenderContext, Fbo, Color, Rectangle
from kivy.clock import Clock
from kivy.uix.floatlayout import FloatLayout
from kivy.factory import Factory
from kivy.core.window import Window

class ShaderSurface(FloatLayout):
    seed = 0.

    def __init__(self, **kwargs):
        self.canvas = RenderContext(use_parent_projection=True, use_parent_modelview=True)
        with self.canvas:
            self.fbo = Fbo(size=Window.size, use_parent_projection=True)

        with self.fbo:
            Color(0,0,0)
            Rectangle(size=Window.size)

        self.texture = self.fbo.texture

        super(ShaderSurface, self).__init__(**kwargs)
        self.keyboard = Window.request_keyboard(self.keyboard_closed, self)
        self.keyboard.bind(on_key_down=self.on_key_down)
        Clock.schedule_once(self.update_shader,-1)

    def keyboard_closed(self):
        self.keyboard.unbind(on_key_down=self.on_key_down)
        self.keyboard = None

    def update_shader(self, dt=0.):
        self.canvas['resolution'] = list(map(float, self.size))
        self.canvas['seed'] = self.seed
        self.canvas.ask_update()

    def on_key_down(self, keyboard, keycode, text, modifiers):
        if keycode[1] == 'spacebar':
            self.seed += 1.
            self.update_shader()
            Window.screenshot()

Factory.register('ShaderSurface', cls=ShaderSurface)

class RendererApp(App):
    def build(self):
        self.root.canvas.shader.source = 'cracks_sub.glsl'

if __name__ == '__main__':
    RendererApp().run()

Arquivo KV:

#:kivy 1.9

ShaderSurface:
    canvas:
        Color:
            rgb: 1, 1, 1
        Rectangle:
            size: self.size
            pos: self.pos
            texture: root.fbo.texture

Código GLSL:

---VERTEX---
uniform vec2        resolution;
in vec2             vPosition;

void main()
{
    gl_Position = vec4(vPosition.xy-resolution/2., 0, 1);
}
---FRAGMENT---
#version 330
precision highp float;

out vec4 frag_color;

uniform vec2 resolution;
uniform float seed;

vec2 tr(vec2 p)
{
    p /= resolution.xy;
    p = -1.0+2.0*p;
    p.y *= resolution.y/resolution.x;
    return p;
}

float hash( float n ){
    return fract(sin(n)*43758.5453);
}

float noise( vec2 uv ){
    vec3 x = vec3(uv, 0);

    vec3 p = floor(x);
    vec3 f = fract(x);

    f       = f*f*(3.0-2.0*f);
    float n = p.x + p.y*57.0 + 113.0*p.z;

    return mix(mix(mix( hash(n+0.0), hash(n+1.0),f.x),
                   mix( hash(n+57.0), hash(n+58.0),f.x),f.y),
               mix(mix( hash(n+113.0), hash(n+114.0),f.x),
                   mix( hash(n+170.0), hash(n+171.0),f.x),f.y),f.z);
}

mat2 m = mat2(0.8,0.6,-0.6,0.8);

float fbm(vec2 p)
{
    float f = 0.0;
    f += 0.5000*noise( p ); p*=m*2.02;
    f += 0.2500*noise( p ); p*=m*2.03;
    f += 0.1250*noise( p ); p*=m*2.01;
    f += 0.0625*noise( p );
    f /= 0.9375;
    return f;
}

vec2 hash2( vec2 p )
{
    return fract(sin(vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))))*43758.5453);
}

float voronoi(vec2 x, out vec2 rt)
{
    vec2 p = floor(x);
    vec2 f = fract(x);

    vec2 mb, mr;

    float res = 8.0;
    for( int j=-1; j<=1; j++)
    for( int i=-1; i<=1; i++)
    {
        vec2 b = vec2(float(i),float(j));
        vec2 r = b+hash2(p+b)-f;
        float d = dot(r,r);

        if( d<res )
        {
            res = d;
            mr = r;
            mb = b;
            rt=r;
        }
    }


    res = 8.0;
    for( int j=-2; j<=2; j++ )
    for( int i=-2; i<=2; i++ )
    {
        vec2 b = mb + vec2(float(i),float(j));
        vec2 r = b + hash2(p+b)-f;
        float d = dot((res*res)*(mr+r),normalize(r-mr));

        res = min(res,d);
    }


    return res;
}

float crack(vec2 p)
{
    float g = mod(seed,65536./4.);
    p.x+=g;
    p.y-=seed-g;
    p.y*=1.3;
    p.x+=noise(p*4.)*.08;
    float k = 0.;
    vec2 rb = vec2(.0);
    k=voronoi(p*2.,rb);
    k=smoothstep(.0,.3,k*.05);
    float v = 0.;
    v=voronoi(rb*4.,rb);
    v=smoothstep(.0,.5,v*.05);
    k*=v;
    k-=fbm(p*128.)*.3;
    return k;
}

void main( void )
{
    vec2 fc = gl_FragCoord.xy;
    vec2 p = tr(fc);
    vec3 col = vec3(.39,.37,.25);

    vec3 abb = vec3(.14,.12,.10)/5.;

    p*=(1.+length(p)*.1);

    col.r*=crack(vec2(p.x+abb.x,p.y));
    col.g*=crack(vec2(p.x+abb.y,p.y));
    col.b*=crack(vec2(p.x+abb.z,p.y));

    col*=smoothstep(4.,1.2,dot(p,p));
    col*=exp(.66);

    //col=vec3(crack(p));
    frag_color = vec4(col,1.);
}

A função voronoi no código GLSL é de Íñigo Quílez. Todo cálculo relacionado a voronoi acontece inteiramente no shader de fragmento, com algumas funções de ruído de procedimento para criar manchas e perturbar um pouco as linhas do padrão de voronoi.

Ao pressionar o espaço, a semente será aumentada em 1 e uma nova imagem será gerada e salva como um .pngarquivo.

Atualização: distorção de lente adicionada, vinhetas e aberração cromática para torná-lo mais foto-realista. Adicionado sub-voronoi padrão.

Gábor Fekete
fonte
Isso também pode receber uma semente como entrada?
Trichoplax
A classe ShaderSurface tem um membro da classe seed. Isso será canalizado para o shader como uma variável flutuante uniforme. Na função de trinca do shader, a semente é usada para traduzir o ponto pelo valor da semente.
Gábor Fekete 27/06
1

Java

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import java.util.Scanner;

import javax.imageio.ImageIO;

public class CrackedSoil {
    static BufferedImage b;
    static Random rand;
    public static int distance(int col1,int col2){
        Color a=new Color(col1);
        Color b=new Color(col2);
        return (int)(Math.pow(a.getRed()-b.getRed(), 2)+Math.pow(a.getGreen()-b.getGreen(), 2)+Math.pow(a.getBlue()-b.getBlue(), 2));
    }
    public static void edge(){
        boolean[][] edges=new boolean[500][500];
        int threshold=125+rand.nextInt(55);
        for(int x=1;x<499;x++){
            for(int y=1;y<499;y++){
                int rgb=b.getRGB(x, y);
                int del=0;
                for(int i=-1;i<=1;i++){
                    for(int j=-1;i<=j;i++){
                        del+=distance(rgb,b.getRGB(x+i, y+j));
                    }
                }
                edges[x][y]=del>threshold;
            }
        }
        for(int x=0;x<500;x++){
            for(int y=0;y<500;y++){
                if(edges[x][y])b.setRGB(x, y,new Color(4+rand.nextInt(4),4+rand.nextInt(4),4+rand.nextInt(4)).getRGB());
            }
        }
    }
    public static void main(String[]arg) throws IOException{
        b=new BufferedImage(500,500,BufferedImage.TYPE_INT_RGB);
        Scanner scanner=new Scanner(System.in);
        rand=new Random(scanner.nextInt());
        int numPoints=10+rand.nextInt(15);
        Color[]c=new Color[numPoints];
        int[][]ints=new int[numPoints][2];
        int[]weights=new int[numPoints];
        for(int i=0;i<numPoints;i++){
            switch(i%4){
            case 0:ints[i]=new int[]{251+rand.nextInt(240),7+rand.nextInt(240)};break;
            case 1:ints[i]=new int[]{7+rand.nextInt(240),7+rand.nextInt(240)};break;
            case 2:ints[i]=new int[]{7+rand.nextInt(240),251+rand.nextInt(240)};break;
            case 3:ints[i]=new int[]{251+rand.nextInt(240),251+rand.nextInt(240)};break;
            }

            c[i]=new Color(40+rand.nextInt(200),40+rand.nextInt(200),40+rand.nextInt(200));
            weights[i]=50+rand.nextInt(15);
        }
        for(int x=0;x<500;x++){
            for(int y=0;y<500;y++){
                double d=999999;
                Color col=Color.BLACK;
                for(int i=0;i<numPoints;i++){
                    double d2=weights[i]*Math.sqrt((x-ints[i][0])*(x-ints[i][0])+(y-ints[i][1])*(y-ints[i][1]));
                    if(d2<d){
                        d=d2;
                        col=c[i];
                    }
                }
                b.setRGB(x, y,col.getRGB());
            }
        }
        //ImageIO.write(b,"png",new File("voronoi1.png"));
        for(int i=0;i<numPoints/3;i++){
            ints[i]=new int[]{7+rand.nextInt(490),7+rand.nextInt(490)};
            c[i]=new Color(40+rand.nextInt(200),40+rand.nextInt(200),40+rand.nextInt(200));
            weights[i]=50+rand.nextInt(5);
        }
        for(int x=0;x<500;x++){
            for(int y=0;y<500;y++){
                double d=999999;
                Color col=Color.BLACK;
                for(int i=0;i<numPoints/3;i++){
                    double d2=weights[i]*Math.sqrt((x-ints[i][0])*(x-ints[i][0])+(y-ints[i][1])*(y-ints[i][1]));
                    if(d2<d){
                        d=d2;
                        col=c[i];
                    }
                }
                Color col3=new Color(b.getRGB(x, y));
                b.setRGB(x, y,new Color((col3.getRed()+col.getRed()*3)/4,(col3.getGreen()+col.getGreen()*3)/4,(col3.getBlue()+col.getBlue()*3)/4).getRGB());
            }
        }
        //ImageIO.write(b,"png",new File("voronoi2.png"));
        for(int i=2+rand.nextInt(3);i>0;i--)edge();
        //ImageIO.write(b,"png",new File("voronoi_edge.png"));
        for(int x=0;x<500;x++){
            for(int y=0;y<500;y++){
                Color col=new Color(b.getRGB(x, y));
                if(col.getRed()+col.getBlue()+col.getGreen()>50){
                    if(rand.nextDouble()<0.95){
                        b.setRGB(x, y,new Color(150+rand.nextInt(9),145+rand.nextInt(9),135+rand.nextInt(9)).getRGB());
                    }else{
                        b.setRGB(x, y,new Color(120+col.getRed()/7+rand.nextInt(12),115+col.getGreen()/7+rand.nextInt(12),105+col.getBlue()/7+rand.nextInt(12)).getRGB());
                    }
                }
            }
        }
        ImageIO.write(b,"png",new File("soil.png"));
    }
}

Cria um composto de dois diagramas aleatórios, que são executados através de uma simples detecção de aresta e finalmente convertidos no resultado final.

Algumas saídas:

insira a descrição da imagem aqui

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Algumas das etapas intermediárias para essa última:

insira a descrição da imagem aqui

(O primeiro diagrama voronoi)

insira a descrição da imagem aqui

(O composto dos dois diagramas de voronoi)

insira a descrição da imagem aqui

(Após a etapa de detecção da borda, mas antes da recoloração final)

SuperJedi224
fonte