Обробка
Оновлення! 4096x4096 зображень!
Я з’єднав свою другу посаду в цю, поєднавши дві програми разом.
Повну колекцію вибраних зображень можна знайти тут, на Dropbox . (Примітка. DropBox не може генерувати попередній перегляд зображень 4096x4096; просто натисніть на них та натисніть "Завантажити").
Якщо ви дивитесь лише на один погляд на цей (плитковий)! Тут він зменшений (та багато іншого нижче), оригінал 2048x1024:
Ця програма працює, гуляючи доріжки від випадково вибраних точок у кольоровому кубі, потім малюючи їх у довільно вибраних контурах на зображенні. Варіантів дуже багато. Параметри, які можна налаштувати:
- Максимальна довжина шляху кольорового куба.
- Максимальний крок за допомогою кольорового куба (більші значення спричиняють більшу дисперсію, але мінімізують кількість малих доріжок до кінця, коли речі затягуються).
- Плитка зображення.
- Наразі існує два режими контуру зображення:
- Режим 1 (режим цієї оригінальної публікації): Знаходить блок невикористаних пікселів на зображенні та надає цьому блоку. Блоки можуть бути як випадковим чином, так і впорядковані зліва направо.
- Режим 2 (режим мого другого допису, який я об'єднав у цей): Вибирає випадкову початкову точку на зображенні та проходить шлях шляхом невикористаних пікселів; може гуляти використані пікселі. Параметри цього режиму:
- Набір напрямків для прогулянки (ортогональна, діагональна або обидві).
- Чи потрібно змінювати напрямок (в даний час за годинниковою стрілкою, але код є гнучким) після кожного кроку, або змінювати напрям лише після зустрічі із зайнятим пікселем ..
- Можливість переміщення порядку переміщення (замість годинникової стрілки) змінюється.
Він працює для всіх розмірів до 4096x4096.
Повний ескіз обробки можна знайти тут: Tracer.zip
Я вставив усі файли в один і той же блок коду нижче, щоб заощадити місце (навіть усі в одному файлі, це все-таки дійсна ескіз). Якщо ви хочете скористатися однією з пресетів, змініть індекс у gPreset
призначенні. Якщо це запустити в обробці, ви можете натиснути, r
поки він працює, щоб створити нове зображення.
- Оновлення 1: Оптимізований код для відстеження перших невикористаних кольорів / пікселів і не для пошуку за відомими використаними пікселями; скоротив час генерації 2048x1024 з 10-30 хвилин до приблизно 15 секунд, а 4096x4096 з 1-3 годин до приблизно 1 хвилини. Джерело випадаючої скриньки та джерело нижче оновлено.
- Оновлення 2: Виправлена помилка, яка заважала генерувати зображення 4096x4096.
final int BITS = 5; // Set to 5, 6, 7, or 8!
// Preset (String name, int colorBits, int maxCubePath, int maxCubeStep, int imageMode, int imageOpts)
final Preset[] PRESETS = new Preset[] {
// 0
new Preset("flowers", BITS, 8*32*32, 2, ImageRect.MODE2, ImageRect.ALL_CW | ImageRect.CHANGE_DIRS),
new Preset("diamonds", BITS, 2*32*32, 2, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.CHANGE_DIRS),
new Preset("diamondtile", BITS, 2*32*32, 2, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.CHANGE_DIRS | ImageRect.WRAP),
new Preset("shards", BITS, 2*32*32, 2, ImageRect.MODE2, ImageRect.ALL_CW | ImageRect.CHANGE_DIRS | ImageRect.SHUFFLE_DIRS),
new Preset("bigdiamonds", BITS, 100000, 6, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.CHANGE_DIRS),
// 5
new Preset("bigtile", BITS, 100000, 6, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.CHANGE_DIRS | ImageRect.WRAP),
new Preset("boxes", BITS, 32*32, 2, ImageRect.MODE2, ImageRect.ORTHO_CW),
new Preset("giftwrap", BITS, 32*32, 2, ImageRect.MODE2, ImageRect.ORTHO_CW | ImageRect.WRAP),
new Preset("diagover", BITS, 32*32, 2, ImageRect.MODE2, ImageRect.DIAG_CW),
new Preset("boxfade", BITS, 32*32, 2, ImageRect.MODE2, ImageRect.DIAG_CW | ImageRect.CHANGE_DIRS),
// 10
new Preset("randlimit", BITS, 512, 2, ImageRect.MODE1, ImageRect.RANDOM_BLOCKS),
new Preset("ordlimit", BITS, 64, 2, ImageRect.MODE1, 0),
new Preset("randtile", BITS, 2048, 3, ImageRect.MODE1, ImageRect.RANDOM_BLOCKS | ImageRect.WRAP),
new Preset("randnolimit", BITS, 1000000, 1, ImageRect.MODE1, ImageRect.RANDOM_BLOCKS),
new Preset("ordnolimit", BITS, 1000000, 1, ImageRect.MODE1, 0)
};
PGraphics gFrameBuffer;
Preset gPreset = PRESETS[2];
void generate () {
ColorCube cube = gPreset.createCube();
ImageRect image = gPreset.createImage();
gFrameBuffer = createGraphics(gPreset.getWidth(), gPreset.getHeight(), JAVA2D);
gFrameBuffer.noSmooth();
gFrameBuffer.beginDraw();
while (!cube.isExhausted())
image.drawPath(cube.nextPath(), gFrameBuffer);
gFrameBuffer.endDraw();
if (gPreset.getName() != null)
gFrameBuffer.save(gPreset.getName() + "_" + gPreset.getCubeSize() + ".png");
//image.verifyExhausted();
//cube.verifyExhausted();
}
void setup () {
size(gPreset.getDisplayWidth(), gPreset.getDisplayHeight());
noSmooth();
generate();
}
void keyPressed () {
if (key == 'r' || key == 'R')
generate();
}
boolean autogen = false;
int autop = 0;
int autob = 5;
void draw () {
if (autogen) {
gPreset = new Preset(PRESETS[autop], autob);
generate();
if ((++ autop) >= PRESETS.length) {
autop = 0;
if ((++ autob) > 8)
autogen = false;
}
}
if (gPreset.isWrapped()) {
int hw = width/2;
int hh = height/2;
image(gFrameBuffer, 0, 0, hw, hh);
image(gFrameBuffer, hw, 0, hw, hh);
image(gFrameBuffer, 0, hh, hw, hh);
image(gFrameBuffer, hw, hh, hw, hh);
} else {
image(gFrameBuffer, 0, 0, width, height);
}
}
static class ColorStep {
final int r, g, b;
ColorStep (int rr, int gg, int bb) { r=rr; g=gg; b=bb; }
}
class ColorCube {
final boolean[] used;
final int size;
final int maxPathLength;
final ArrayList<ColorStep> allowedSteps = new ArrayList<ColorStep>();
int remaining;
int pathr = -1, pathg, pathb;
int firstUnused = 0;
ColorCube (int size, int maxPathLength, int maxStep) {
this.used = new boolean[size*size*size];
this.remaining = size * size * size;
this.size = size;
this.maxPathLength = maxPathLength;
for (int r = -maxStep; r <= maxStep; ++ r)
for (int g = -maxStep; g <= maxStep; ++ g)
for (int b = -maxStep; b <= maxStep; ++ b)
if (r != 0 && g != 0 && b != 0)
allowedSteps.add(new ColorStep(r, g, b));
}
boolean isExhausted () {
println(remaining);
return remaining <= 0;
}
boolean isUsed (int r, int g, int b) {
if (r < 0 || r >= size || g < 0 || g >= size || b < 0 || b >= size)
return true;
else
return used[(r*size+g)*size+b];
}
void setUsed (int r, int g, int b) {
used[(r*size+g)*size+b] = true;
}
int nextColor () {
if (pathr == -1) { // Need to start a new path.
// Limit to 50 attempts at random picks; things get tight near end.
for (int n = 0; n < 50 && pathr == -1; ++ n) {
int r = (int)random(size);
int g = (int)random(size);
int b = (int)random(size);
if (!isUsed(r, g, b)) {
pathr = r;
pathg = g;
pathb = b;
}
}
// If we didn't find one randomly, just search for one.
if (pathr == -1) {
final int sizesq = size*size;
final int sizemask = size - 1;
for (int rgb = firstUnused; rgb < size*size*size; ++ rgb) {
pathr = (rgb/sizesq)&sizemask;//(rgb >> 10) & 31;
pathg = (rgb/size)&sizemask;//(rgb >> 5) & 31;
pathb = rgb&sizemask;//rgb & 31;
if (!used[rgb]) {
firstUnused = rgb;
break;
}
}
}
assert(pathr != -1);
} else { // Continue moving on existing path.
// Find valid next path steps.
ArrayList<ColorStep> possibleSteps = new ArrayList<ColorStep>();
for (ColorStep step:allowedSteps)
if (!isUsed(pathr+step.r, pathg+step.g, pathb+step.b))
possibleSteps.add(step);
// If there are none end this path.
if (possibleSteps.isEmpty()) {
pathr = -1;
return -1;
}
// Otherwise pick a random step and move there.
ColorStep s = possibleSteps.get((int)random(possibleSteps.size()));
pathr += s.r;
pathg += s.g;
pathb += s.b;
}
setUsed(pathr, pathg, pathb);
return 0x00FFFFFF & color(pathr * (256/size), pathg * (256/size), pathb * (256/size));
}
ArrayList<Integer> nextPath () {
ArrayList<Integer> path = new ArrayList<Integer>();
int rgb;
while ((rgb = nextColor()) != -1) {
path.add(0xFF000000 | rgb);
if (path.size() >= maxPathLength) {
pathr = -1;
break;
}
}
remaining -= path.size();
//assert(!path.isEmpty());
if (path.isEmpty()) {
println("ERROR: empty path.");
verifyExhausted();
}
return path;
}
void verifyExhausted () {
final int sizesq = size*size;
final int sizemask = size - 1;
for (int rgb = 0; rgb < size*size*size; ++ rgb) {
if (!used[rgb]) {
int r = (rgb/sizesq)&sizemask;
int g = (rgb/size)&sizemask;
int b = rgb&sizemask;
println("UNUSED COLOR: " + r + " " + g + " " + b);
}
}
if (remaining != 0)
println("REMAINING COLOR COUNT IS OFF: " + remaining);
}
}
static class ImageStep {
final int x;
final int y;
ImageStep (int xx, int yy) { x=xx; y=yy; }
}
static int nmod (int a, int b) {
return (a % b + b) % b;
}
class ImageRect {
// for mode 1:
// one of ORTHO_CW, DIAG_CW, ALL_CW
// or'd with flags CHANGE_DIRS
static final int ORTHO_CW = 0;
static final int DIAG_CW = 1;
static final int ALL_CW = 2;
static final int DIR_MASK = 0x03;
static final int CHANGE_DIRS = (1<<5);
static final int SHUFFLE_DIRS = (1<<6);
// for mode 2:
static final int RANDOM_BLOCKS = (1<<0);
// for both modes:
static final int WRAP = (1<<16);
static final int MODE1 = 0;
static final int MODE2 = 1;
final boolean[] used;
final int width;
final int height;
final boolean changeDir;
final int drawMode;
final boolean randomBlocks;
final boolean wrap;
final ArrayList<ImageStep> allowedSteps = new ArrayList<ImageStep>();
// X/Y are tracked instead of index to preserve original unoptimized mode 1 behavior
// which does column-major searches instead of row-major.
int firstUnusedX = 0;
int firstUnusedY = 0;
ImageRect (int width, int height, int drawMode, int drawOpts) {
boolean myRandomBlocks = false, myChangeDir = false;
this.used = new boolean[width*height];
this.width = width;
this.height = height;
this.drawMode = drawMode;
this.wrap = (drawOpts & WRAP) != 0;
if (drawMode == MODE1) {
myRandomBlocks = (drawOpts & RANDOM_BLOCKS) != 0;
} else if (drawMode == MODE2) {
myChangeDir = (drawOpts & CHANGE_DIRS) != 0;
switch (drawOpts & DIR_MASK) {
case ORTHO_CW:
allowedSteps.add(new ImageStep(1, 0));
allowedSteps.add(new ImageStep(0, -1));
allowedSteps.add(new ImageStep(-1, 0));
allowedSteps.add(new ImageStep(0, 1));
break;
case DIAG_CW:
allowedSteps.add(new ImageStep(1, -1));
allowedSteps.add(new ImageStep(-1, -1));
allowedSteps.add(new ImageStep(-1, 1));
allowedSteps.add(new ImageStep(1, 1));
break;
case ALL_CW:
allowedSteps.add(new ImageStep(1, 0));
allowedSteps.add(new ImageStep(1, -1));
allowedSteps.add(new ImageStep(0, -1));
allowedSteps.add(new ImageStep(-1, -1));
allowedSteps.add(new ImageStep(-1, 0));
allowedSteps.add(new ImageStep(-1, 1));
allowedSteps.add(new ImageStep(0, 1));
allowedSteps.add(new ImageStep(1, 1));
break;
}
if ((drawOpts & SHUFFLE_DIRS) != 0)
java.util.Collections.shuffle(allowedSteps);
}
this.randomBlocks = myRandomBlocks;
this.changeDir = myChangeDir;
}
boolean isUsed (int x, int y) {
if (wrap) {
x = nmod(x, width);
y = nmod(y, height);
}
if (x < 0 || x >= width || y < 0 || y >= height)
return true;
else
return used[y*width+x];
}
boolean isUsed (int x, int y, ImageStep d) {
return isUsed(x + d.x, y + d.y);
}
void setUsed (int x, int y) {
if (wrap) {
x = nmod(x, width);
y = nmod(y, height);
}
used[y*width+x] = true;
}
boolean isBlockFree (int x, int y, int w, int h) {
for (int yy = y; yy < y + h; ++ yy)
for (int xx = x; xx < x + w; ++ xx)
if (isUsed(xx, yy))
return false;
return true;
}
void drawPath (ArrayList<Integer> path, PGraphics buffer) {
if (drawMode == MODE1)
drawPath1(path, buffer);
else if (drawMode == MODE2)
drawPath2(path, buffer);
}
void drawPath1 (ArrayList<Integer> path, PGraphics buffer) {
int w = (int)(sqrt(path.size()) + 0.5);
if (w < 1) w = 1; else if (w > width) w = width;
int h = (path.size() + w - 1) / w;
int x = -1, y = -1;
int woff = wrap ? 0 : (1 - w);
int hoff = wrap ? 0 : (1 - h);
// Try up to 50 times to find a random location for block.
if (randomBlocks) {
for (int n = 0; n < 50 && x == -1; ++ n) {
int xx = (int)random(width + woff);
int yy = (int)random(height + hoff);
if (isBlockFree(xx, yy, w, h)) {
x = xx;
y = yy;
}
}
}
// If random choice failed just search for one.
int starty = firstUnusedY;
for (int xx = firstUnusedX; xx < width + woff && x == -1; ++ xx) {
for (int yy = starty; yy < height + hoff && x == -1; ++ yy) {
if (isBlockFree(xx, yy, w, h)) {
firstUnusedX = x = xx;
firstUnusedY = y = yy;
}
}
starty = 0;
}
if (x != -1) {
for (int xx = x, pathn = 0; xx < x + w && pathn < path.size(); ++ xx)
for (int yy = y; yy < y + h && pathn < path.size(); ++ yy, ++ pathn) {
buffer.set(nmod(xx, width), nmod(yy, height), path.get(pathn));
setUsed(xx, yy);
}
} else {
for (int yy = 0, pathn = 0; yy < height && pathn < path.size(); ++ yy)
for (int xx = 0; xx < width && pathn < path.size(); ++ xx)
if (!isUsed(xx, yy)) {
buffer.set(nmod(xx, width), nmod(yy, height), path.get(pathn));
setUsed(xx, yy);
++ pathn;
}
}
}
void drawPath2 (ArrayList<Integer> path, PGraphics buffer) {
int pathn = 0;
while (pathn < path.size()) {
int x = -1, y = -1;
// pick a random location in the image (try up to 100 times before falling back on search)
for (int n = 0; n < 100 && x == -1; ++ n) {
int xx = (int)random(width);
int yy = (int)random(height);
if (!isUsed(xx, yy)) {
x = xx;
y = yy;
}
}
// original:
//for (int yy = 0; yy < height && x == -1; ++ yy)
// for (int xx = 0; xx < width && x == -1; ++ xx)
// if (!isUsed(xx, yy)) {
// x = xx;
// y = yy;
// }
// optimized:
if (x == -1) {
for (int n = firstUnusedY * width + firstUnusedX; n < used.length; ++ n) {
if (!used[n]) {
firstUnusedX = x = (n % width);
firstUnusedY = y = (n / width);
break;
}
}
}
// start drawing
int dir = 0;
while (pathn < path.size()) {
buffer.set(nmod(x, width), nmod(y, height), path.get(pathn ++));
setUsed(x, y);
int diro;
for (diro = 0; diro < allowedSteps.size(); ++ diro) {
int diri = (dir + diro) % allowedSteps.size();
ImageStep step = allowedSteps.get(diri);
if (!isUsed(x, y, step)) {
dir = diri;
x += step.x;
y += step.y;
break;
}
}
if (diro == allowedSteps.size())
break;
if (changeDir)
++ dir;
}
}
}
void verifyExhausted () {
for (int n = 0; n < used.length; ++ n)
if (!used[n])
println("UNUSED IMAGE PIXEL: " + (n%width) + " " + (n/width));
}
}
class Preset {
final String name;
final int cubeSize;
final int maxCubePath;
final int maxCubeStep;
final int imageWidth;
final int imageHeight;
final int imageMode;
final int imageOpts;
final int displayScale;
Preset (Preset p, int colorBits) {
this(p.name, colorBits, p.maxCubePath, p.maxCubeStep, p.imageMode, p.imageOpts);
}
Preset (String name, int colorBits, int maxCubePath, int maxCubeStep, int imageMode, int imageOpts) {
final int csize[] = new int[]{ 32, 64, 128, 256 };
final int iwidth[] = new int[]{ 256, 512, 2048, 4096 };
final int iheight[] = new int[]{ 128, 512, 1024, 4096 };
final int dscale[] = new int[]{ 2, 1, 1, 1 };
this.name = name;
this.cubeSize = csize[colorBits - 5];
this.maxCubePath = maxCubePath;
this.maxCubeStep = maxCubeStep;
this.imageWidth = iwidth[colorBits - 5];
this.imageHeight = iheight[colorBits - 5];
this.imageMode = imageMode;
this.imageOpts = imageOpts;
this.displayScale = dscale[colorBits - 5];
}
ColorCube createCube () {
return new ColorCube(cubeSize, maxCubePath, maxCubeStep);
}
ImageRect createImage () {
return new ImageRect(imageWidth, imageHeight, imageMode, imageOpts);
}
int getWidth () {
return imageWidth;
}
int getHeight () {
return imageHeight;
}
int getDisplayWidth () {
return imageWidth * displayScale * (isWrapped() ? 2 : 1);
}
int getDisplayHeight () {
return imageHeight * displayScale * (isWrapped() ? 2 : 1);
}
String getName () {
return name;
}
int getCubeSize () {
return cubeSize;
}
boolean isWrapped () {
return (imageOpts & ImageRect.WRAP) != 0;
}
}
Ось повний набір зображень 256х128, які мені подобаються:
Режим 1:
Мій улюблений з оригінального набору (max_path_length = 512, path_step = 2, випадковий, відображається 2х, посилання 256x128 ):
Інші (ліві два впорядковані, праворуч два випадкові, два верхніх довжина шляху обмежена, два знизу необмежені):
Це може бути викладено плиткою:
Режим 2:
Їх можна викласти плиткою:
512x512 виділень:
Алмази, що зберігаються, мої улюблені з режиму 2; ви можете бачити, як шляхи обходять наявні об'єкти:
Більший крок шляху та максимальна довжина шляху, плитка:
Випадковий режим 1, розміщений на таблиці:
Більше вибору:
Усі відображення 512x512 можна знайти в папці "dropbox" (* _64.png).
2048x1024 та 4096x4096:
Вони занадто великі для вбудовування, і всі хости зображень, які я знайшов, знижують їх до 1600x1200. На даний момент я рендерую набір зображень 4096x4096, тому більше буде доступно найближчим часом. Замість того, щоб сюди включити всі посилання, просто перейдіть, перегляньте їх у папці випадок (* _128.png та * _256.png, зверніть увагу: 4096x4096 - вони занадто великі для попереднього перегляду вікон, просто натисніть «завантажити»). Ось деякі з моїх улюблених:
2048x1024 великі кахельні алмази (той самий, з яким я пов’язаний на початку цієї публікації)
2048x1024 алмази (я люблю цей!), Зменшено :
4096x4096 великих алмазних плиток (Нарешті! Клацніть «скачати» у посиланні Dropbox; він занадто великий для їх перегляду), зменшений вниз:
4096x4096 випадковий режим 1 :
4096x4096 ще один класний
Оновлення: Набір попередньо встановлених зображень розміром 2048x1024 готовий і знаходиться у вікні. Набір 4096x4096 слід виконати протягом години.
Є багато хороших, мені дуже важко вибрати, які з них публікувати, тому, будь ласка, перевірте посилання на папку!