Tratamiento
¡Actualizar! 4096x4096 imágenes!
He fusionado mi segunda publicación en esta combinando los dos programas.
Puede encontrar una colección completa de imágenes seleccionadas aquí, en Dropbox . (Nota: DropBox no puede generar vistas previas para las imágenes 4096x4096; simplemente haga clic en ellas y luego haga clic en "Descargar").
¡Si solo miras un vistazo a este (enlosable)! Aquí se reduce (y muchos más a continuación), original 2048x1024:
Este programa funciona caminando rutas desde puntos seleccionados al azar en el cubo de color, luego dibujándolos en rutas seleccionadas al azar en la imagen. Hay muchas posibilidades. Las opciones configurables son:
- Longitud máxima del camino del cubo de color.
- Paso máximo para atravesar el cubo de color (los valores más grandes causan una mayor variación pero minimizan el número de caminos pequeños hacia el final cuando las cosas se ponen apretadas).
- Mosaico de la imagen.
- Actualmente hay dos modos de ruta de imagen:
- Modo 1 (el modo de esta publicación original): encuentra un bloque de píxeles no utilizados en la imagen y lo representa en ese bloque. Los bloques pueden ubicarse al azar u ordenarse de izquierda a derecha.
- Modo 2 (el modo de mi segunda publicación que fusioné con esta): selecciona un punto de inicio aleatorio en la imagen y recorre un camino a través de píxeles no utilizados; puede caminar alrededor de píxeles usados. Opciones para este modo:
- Conjunto de direcciones para caminar (ortogonal, diagonal o ambas).
- Si cambiar o no la dirección (actualmente en el sentido de las agujas del reloj pero el código es flexible) después de cada paso, o solo cambiar la dirección al encontrar un píxel ocupado.
- Opción para mezclar el orden de los cambios de dirección (en lugar de en sentido horario)
Funciona para todos los tamaños hasta 4096x4096.
El boceto completo de procesamiento se puede encontrar aquí: Tracer.zip
He pegado todos los archivos en el mismo bloque de código a continuación solo para ahorrar espacio (incluso todos en un solo archivo, sigue siendo un boceto válido). Si desea utilizar uno de los ajustes preestablecidos, cambie el índice en la gPreset
asignación. Si ejecuta esto en Processing, puede presionar r
mientras se está ejecutando para generar una nueva imagen.
- Actualización 1: Código optimizado para rastrear el primer color / píxel no utilizado y no buscar en píxeles usados conocidos; redujo el tiempo de generación de 2048x1024 de 10-30 minutos a aproximadamente 15 segundos, y 4096x4096 de 1-3 horas a aproximadamente 1 minuto. Fuente del cuadro desplegable y fuente actualizada a continuación.
- Actualización 2: Se corrigió el error que impedía que se generaran imágenes de 4096x4096.
final int BITS = 5; // Set to 5, 6, 7, or 8!
// Preset (String name, int colorBits, int maxCubePath, int maxCubeStep, int imageMode, int imageOpts)
final Preset[] PRESETS = new Preset[] {
// 0
new Preset("flowers", BITS, 8*32*32, 2, ImageRect.MODE2, ImageRect.ALL_CW | ImageRect.CHANGE_DIRS),
new Preset("diamonds", BITS, 2*32*32, 2, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.CHANGE_DIRS),
new Preset("diamondtile", BITS, 2*32*32, 2, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.CHANGE_DIRS | ImageRect.WRAP),
new Preset("shards", BITS, 2*32*32, 2, ImageRect.MODE2, ImageRect.ALL_CW | ImageRect.CHANGE_DIRS | ImageRect.SHUFFLE_DIRS),
new Preset("bigdiamonds", BITS, 100000, 6, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.CHANGE_DIRS),
// 5
new Preset("bigtile", BITS, 100000, 6, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.CHANGE_DIRS | ImageRect.WRAP),
new Preset("boxes", BITS, 32*32, 2, ImageRect.MODE2, ImageRect.ORTHO_CW),
new Preset("giftwrap", BITS, 32*32, 2, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.WRAP),
new Preset("diagover", BITS, 32*32, 2, ImageRect.MODE2, ImageRect.DIAG_CW),
new Preset("boxfade", BITS, 32*32, 2, ImageRect.MODE2, ImageRect.DIAG_CW | ImageRect.CHANGE_DIRS),
// 10
new Preset("randlimit", BITS, 512, 2, ImageRect.MODE1, ImageRect.RANDOM_BLOCKS),
new Preset("ordlimit", BITS, 64, 2, ImageRect.MODE1, 0),
new Preset("randtile", BITS, 2048, 3, ImageRect.MODE1, ImageRect.RANDOM_BLOCKS | ImageRect.WRAP),
new Preset("randnolimit", BITS, 1000000, 1, ImageRect.MODE1, ImageRect.RANDOM_BLOCKS),
new Preset("ordnolimit", BITS, 1000000, 1, ImageRect.MODE1, 0)
};
PGraphics gFrameBuffer;
Preset gPreset = PRESETS[2];
void generate () {
ColorCube cube = gPreset.createCube();
ImageRect image = gPreset.createImage();
gFrameBuffer = createGraphics(gPreset.getWidth(), gPreset.getHeight(), JAVA2D);
gFrameBuffer.noSmooth();
gFrameBuffer.beginDraw();
while (!cube.isExhausted())
image.drawPath(cube.nextPath(), gFrameBuffer);
gFrameBuffer.endDraw();
if (gPreset.getName() != null)
gFrameBuffer.save(gPreset.getName() + "_" + gPreset.getCubeSize() + ".png");
//image.verifyExhausted();
//cube.verifyExhausted();
}
void setup () {
size(gPreset.getDisplayWidth(), gPreset.getDisplayHeight());
noSmooth();
generate();
}
void keyPressed () {
if (key == 'r' || key == 'R')
generate();
}
boolean autogen = false;
int autop = 0;
int autob = 5;
void draw () {
if (autogen) {
gPreset = new Preset(PRESETS[autop], autob);
generate();
if ((++ autop) >= PRESETS.length) {
autop = 0;
if ((++ autob) > 8)
autogen = false;
}
}
if (gPreset.isWrapped()) {
int hw = width/2;
int hh = height/2;
image(gFrameBuffer, 0, 0, hw, hh);
image(gFrameBuffer, hw, 0, hw, hh);
image(gFrameBuffer, 0, hh, hw, hh);
image(gFrameBuffer, hw, hh, hw, hh);
} else {
image(gFrameBuffer, 0, 0, width, height);
}
}
static class ColorStep {
final int r, g, b;
ColorStep (int rr, int gg, int bb) { r=rr; g=gg; b=bb; }
}
class ColorCube {
final boolean[] used;
final int size;
final int maxPathLength;
final ArrayList<ColorStep> allowedSteps = new ArrayList<ColorStep>();
int remaining;
int pathr = -1, pathg, pathb;
int firstUnused = 0;
ColorCube (int size, int maxPathLength, int maxStep) {
this.used = new boolean[size*size*size];
this.remaining = size * size * size;
this.size = size;
this.maxPathLength = maxPathLength;
for (int r = -maxStep; r <= maxStep; ++ r)
for (int g = -maxStep; g <= maxStep; ++ g)
for (int b = -maxStep; b <= maxStep; ++ b)
if (r != 0 && g != 0 && b != 0)
allowedSteps.add(new ColorStep(r, g, b));
}
boolean isExhausted () {
println(remaining);
return remaining <= 0;
}
boolean isUsed (int r, int g, int b) {
if (r < 0 || r >= size || g < 0 || g >= size || b < 0 || b >= size)
return true;
else
return used[(r*size+g)*size+b];
}
void setUsed (int r, int g, int b) {
used[(r*size+g)*size+b] = true;
}
int nextColor () {
if (pathr == -1) { // Need to start a new path.
// Limit to 50 attempts at random picks; things get tight near end.
for (int n = 0; n < 50 && pathr == -1; ++ n) {
int r = (int)random(size);
int g = (int)random(size);
int b = (int)random(size);
if (!isUsed(r, g, b)) {
pathr = r;
pathg = g;
pathb = b;
}
}
// If we didn't find one randomly, just search for one.
if (pathr == -1) {
final int sizesq = size*size;
final int sizemask = size - 1;
for (int rgb = firstUnused; rgb < size*size*size; ++ rgb) {
pathr = (rgb/sizesq)&sizemask;//(rgb >> 10) & 31;
pathg = (rgb/size)&sizemask;//(rgb >> 5) & 31;
pathb = rgb&sizemask;//rgb & 31;
if (!used[rgb]) {
firstUnused = rgb;
break;
}
}
}
assert(pathr != -1);
} else { // Continue moving on existing path.
// Find valid next path steps.
ArrayList<ColorStep> possibleSteps = new ArrayList<ColorStep>();
for (ColorStep step:allowedSteps)
if (!isUsed(pathr+step.r, pathg+step.g, pathb+step.b))
possibleSteps.add(step);
// If there are none end this path.
if (possibleSteps.isEmpty()) {
pathr = -1;
return -1;
}
// Otherwise pick a random step and move there.
ColorStep s = possibleSteps.get((int)random(possibleSteps.size()));
pathr += s.r;
pathg += s.g;
pathb += s.b;
}
setUsed(pathr, pathg, pathb);
return 0x00FFFFFF & color(pathr * (256/size), pathg * (256/size), pathb * (256/size));
}
ArrayList<Integer> nextPath () {
ArrayList<Integer> path = new ArrayList<Integer>();
int rgb;
while ((rgb = nextColor()) != -1) {
path.add(0xFF000000 | rgb);
if (path.size() >= maxPathLength) {
pathr = -1;
break;
}
}
remaining -= path.size();
//assert(!path.isEmpty());
if (path.isEmpty()) {
println("ERROR: empty path.");
verifyExhausted();
}
return path;
}
void verifyExhausted () {
final int sizesq = size*size;
final int sizemask = size - 1;
for (int rgb = 0; rgb < size*size*size; ++ rgb) {
if (!used[rgb]) {
int r = (rgb/sizesq)&sizemask;
int g = (rgb/size)&sizemask;
int b = rgb&sizemask;
println("UNUSED COLOR: " + r + " " + g + " " + b);
}
}
if (remaining != 0)
println("REMAINING COLOR COUNT IS OFF: " + remaining);
}
}
static class ImageStep {
final int x;
final int y;
ImageStep (int xx, int yy) { x=xx; y=yy; }
}
static int nmod (int a, int b) {
return (a % b + b) % b;
}
class ImageRect {
// for mode 1:
// one of ORTHO_CW, DIAG_CW, ALL_CW
// or'd with flags CHANGE_DIRS
static final int ORTHO_CW = 0;
static final int DIAG_CW = 1;
static final int ALL_CW = 2;
static final int DIR_MASK = 0x03;
static final int CHANGE_DIRS = (1<<5);
static final int SHUFFLE_DIRS = (1<<6);
// for mode 2:
static final int RANDOM_BLOCKS = (1<<0);
// for both modes:
static final int WRAP = (1<<16);
static final int MODE1 = 0;
static final int MODE2 = 1;
final boolean[] used;
final int width;
final int height;
final boolean changeDir;
final int drawMode;
final boolean randomBlocks;
final boolean wrap;
final ArrayList<ImageStep> allowedSteps = new ArrayList<ImageStep>();
// X/Y are tracked instead of index to preserve original unoptimized mode 1 behavior
// which does column-major searches instead of row-major.
int firstUnusedX = 0;
int firstUnusedY = 0;
ImageRect (int width, int height, int drawMode, int drawOpts) {
boolean myRandomBlocks = false, myChangeDir = false;
this.used = new boolean[width*height];
this.width = width;
this.height = height;
this.drawMode = drawMode;
this.wrap = (drawOpts & WRAP) != 0;
if (drawMode == MODE1) {
myRandomBlocks = (drawOpts & RANDOM_BLOCKS) != 0;
} else if (drawMode == MODE2) {
myChangeDir = (drawOpts & CHANGE_DIRS) != 0;
switch (drawOpts & DIR_MASK) {
case ORTHO_CW:
allowedSteps.add(new ImageStep(1, 0));
allowedSteps.add(new ImageStep(0, -1));
allowedSteps.add(new ImageStep(-1, 0));
allowedSteps.add(new ImageStep(0, 1));
break;
case DIAG_CW:
allowedSteps.add(new ImageStep(1, -1));
allowedSteps.add(new ImageStep(-1, -1));
allowedSteps.add(new ImageStep(-1, 1));
allowedSteps.add(new ImageStep(1, 1));
break;
case ALL_CW:
allowedSteps.add(new ImageStep(1, 0));
allowedSteps.add(new ImageStep(1, -1));
allowedSteps.add(new ImageStep(0, -1));
allowedSteps.add(new ImageStep(-1, -1));
allowedSteps.add(new ImageStep(-1, 0));
allowedSteps.add(new ImageStep(-1, 1));
allowedSteps.add(new ImageStep(0, 1));
allowedSteps.add(new ImageStep(1, 1));
break;
}
if ((drawOpts & SHUFFLE_DIRS) != 0)
java.util.Collections.shuffle(allowedSteps);
}
this.randomBlocks = myRandomBlocks;
this.changeDir = myChangeDir;
}
boolean isUsed (int x, int y) {
if (wrap) {
x = nmod(x, width);
y = nmod(y, height);
}
if (x < 0 || x >= width || y < 0 || y >= height)
return true;
else
return used[y*width+x];
}
boolean isUsed (int x, int y, ImageStep d) {
return isUsed(x + d.x, y + d.y);
}
void setUsed (int x, int y) {
if (wrap) {
x = nmod(x, width);
y = nmod(y, height);
}
used[y*width+x] = true;
}
boolean isBlockFree (int x, int y, int w, int h) {
for (int yy = y; yy < y + h; ++ yy)
for (int xx = x; xx < x + w; ++ xx)
if (isUsed(xx, yy))
return false;
return true;
}
void drawPath (ArrayList<Integer> path, PGraphics buffer) {
if (drawMode == MODE1)
drawPath1(path, buffer);
else if (drawMode == MODE2)
drawPath2(path, buffer);
}
void drawPath1 (ArrayList<Integer> path, PGraphics buffer) {
int w = (int)(sqrt(path.size()) + 0.5);
if (w < 1) w = 1; else if (w > width) w = width;
int h = (path.size() + w - 1) / w;
int x = -1, y = -1;
int woff = wrap ? 0 : (1 - w);
int hoff = wrap ? 0 : (1 - h);
// Try up to 50 times to find a random location for block.
if (randomBlocks) {
for (int n = 0; n < 50 && x == -1; ++ n) {
int xx = (int)random(width + woff);
int yy = (int)random(height + hoff);
if (isBlockFree(xx, yy, w, h)) {
x = xx;
y = yy;
}
}
}
// If random choice failed just search for one.
int starty = firstUnusedY;
for (int xx = firstUnusedX; xx < width + woff && x == -1; ++ xx) {
for (int yy = starty; yy < height + hoff && x == -1; ++ yy) {
if (isBlockFree(xx, yy, w, h)) {
firstUnusedX = x = xx;
firstUnusedY = y = yy;
}
}
starty = 0;
}
if (x != -1) {
for (int xx = x, pathn = 0; xx < x + w && pathn < path.size(); ++ xx)
for (int yy = y; yy < y + h && pathn < path.size(); ++ yy, ++ pathn) {
buffer.set(nmod(xx, width), nmod(yy, height), path.get(pathn));
setUsed(xx, yy);
}
} else {
for (int yy = 0, pathn = 0; yy < height && pathn < path.size(); ++ yy)
for (int xx = 0; xx < width && pathn < path.size(); ++ xx)
if (!isUsed(xx, yy)) {
buffer.set(nmod(xx, width), nmod(yy, height), path.get(pathn));
setUsed(xx, yy);
++ pathn;
}
}
}
void drawPath2 (ArrayList<Integer> path, PGraphics buffer) {
int pathn = 0;
while (pathn < path.size()) {
int x = -1, y = -1;
// pick a random location in the image (try up to 100 times before falling back on search)
for (int n = 0; n < 100 && x == -1; ++ n) {
int xx = (int)random(width);
int yy = (int)random(height);
if (!isUsed(xx, yy)) {
x = xx;
y = yy;
}
}
// original:
//for (int yy = 0; yy < height && x == -1; ++ yy)
// for (int xx = 0; xx < width && x == -1; ++ xx)
// if (!isUsed(xx, yy)) {
// x = xx;
// y = yy;
// }
// optimized:
if (x == -1) {
for (int n = firstUnusedY * width + firstUnusedX; n < used.length; ++ n) {
if (!used[n]) {
firstUnusedX = x = (n % width);
firstUnusedY = y = (n / width);
break;
}
}
}
// start drawing
int dir = 0;
while (pathn < path.size()) {
buffer.set(nmod(x, width), nmod(y, height), path.get(pathn ++));
setUsed(x, y);
int diro;
for (diro = 0; diro < allowedSteps.size(); ++ diro) {
int diri = (dir + diro) % allowedSteps.size();
ImageStep step = allowedSteps.get(diri);
if (!isUsed(x, y, step)) {
dir = diri;
x += step.x;
y += step.y;
break;
}
}
if (diro == allowedSteps.size())
break;
if (changeDir)
++ dir;
}
}
}
void verifyExhausted () {
for (int n = 0; n < used.length; ++ n)
if (!used[n])
println("UNUSED IMAGE PIXEL: " + (n%width) + " " + (n/width));
}
}
class Preset {
final String name;
final int cubeSize;
final int maxCubePath;
final int maxCubeStep;
final int imageWidth;
final int imageHeight;
final int imageMode;
final int imageOpts;
final int displayScale;
Preset (Preset p, int colorBits) {
this(p.name, colorBits, p.maxCubePath, p.maxCubeStep, p.imageMode, p.imageOpts);
}
Preset (String name, int colorBits, int maxCubePath, int maxCubeStep, int imageMode, int imageOpts) {
final int csize[] = new int[]{ 32, 64, 128, 256 };
final int iwidth[] = new int[]{ 256, 512, 2048, 4096 };
final int iheight[] = new int[]{ 128, 512, 1024, 4096 };
final int dscale[] = new int[]{ 2, 1, 1, 1 };
this.name = name;
this.cubeSize = csize[colorBits - 5];
this.maxCubePath = maxCubePath;
this.maxCubeStep = maxCubeStep;
this.imageWidth = iwidth[colorBits - 5];
this.imageHeight = iheight[colorBits - 5];
this.imageMode = imageMode;
this.imageOpts = imageOpts;
this.displayScale = dscale[colorBits - 5];
}
ColorCube createCube () {
return new ColorCube(cubeSize, maxCubePath, maxCubeStep);
}
ImageRect createImage () {
return new ImageRect(imageWidth, imageHeight, imageMode, imageOpts);
}
int getWidth () {
return imageWidth;
}
int getHeight () {
return imageHeight;
}
int getDisplayWidth () {
return imageWidth * displayScale * (isWrapped() ? 2 : 1);
}
int getDisplayHeight () {
return imageHeight * displayScale * (isWrapped() ? 2 : 1);
}
String getName () {
return name;
}
int getCubeSize () {
return cubeSize;
}
boolean isWrapped () {
return (imageOpts & ImageRect.WRAP) != 0;
}
}
Aquí hay un conjunto completo de imágenes de 256x128 que me gustan:
Modo 1:
Mi favorito del conjunto original (max_path_length = 512, path_step = 2, aleatorio, se muestra 2x, enlace 256x128 ):
Otros (izquierda dos ordenados, derecha dos aleatorios, longitud de ruta superior dos limitada, dos inferiores ilimitados):
Este puede ser en mosaico:
Modo 2:
Estos pueden ser en mosaico:
Selecciones 512x512:
Diamantes enlosables, mi favorito del modo 2; Puedes ver en este cómo los caminos recorren los objetos existentes:
Paso de ruta más grande y longitud máxima de ruta, enlosables:
Modo aleatorio 1, enlosable:
Más selecciones:
Todas las representaciones de 512x512 se pueden encontrar en la carpeta de Dropbox (* _64.png).
2048x1024 y 4096x4096:
Estos son demasiado grandes para incrustarlos y todos los hosts de imágenes que encontré los bajan a 1600x1200. Actualmente estoy renderizando un conjunto de imágenes de 4096x4096, por lo que pronto habrá más disponibles. En lugar de incluir todos los enlaces aquí, simplemente échales un vistazo en la carpeta de Dropbox (* _128.png y * _256.png, nota: los 4096x4096 son demasiado grandes para la vista previa de Dropbox, solo haz clic en "descargar"). Sin embargo, estos son algunos de mis favoritos:
2048x1024 diamantes grandes enlosables (el mismo al que me vinculé al comienzo de esta publicación)
2048x1024 diamantes (¡me encanta este!), Reducido:
4096x4096 diamantes enlosables grandes (¡Finalmente! Haga clic en 'descargar' en el enlace de Dropbox; es demasiado grande para su vista previa), a escala reducida:
4096x4096 modo aleatorio 1 :
4096x4096 otro genial
Actualización: el conjunto de imágenes preestablecidas 2048x1024 está terminado y en el cuadro desplegable. El conjunto 4096x4096 debe hacerse dentro de la hora.
Hay toneladas de buenas, me está costando mucho elegir cuáles publicar, ¡así que mira el enlace de la carpeta!