การประมวลผล
อัพเดท!ภาพ 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;
}
}
นี่คือชุดเต็มของภาพ 256x128 ที่ฉันชอบ:
โหมด 1:
รายการโปรดของฉันจากชุดดั้งเดิม (max_path_length = 512, path_step = 2, สุ่ม, แสดง 2x, ลิงก์256x128 ):
อื่น ๆ (สั่งให้เหลือสองคำสั่ง, สุ่มสองตัว, จำกัดความยาวเส้นทางสองอันดับแรก, สองอันดับไม่ จำกัด ):
อันนี้สามารถปูกระเบื้อง:
โหมด 2:
สิ่งเหล่านี้สามารถปูกระเบื้อง:
ตัวเลือก 512x512:
เพชร Tileable, รายการโปรดของฉันจากโหมด 2; คุณสามารถเห็นได้ในวิธีนี้ว่าเส้นทางเดินไปรอบ ๆ วัตถุที่มีอยู่:
ขั้นตอนพา ธ ที่ใหญ่ขึ้นและความยาวพา ธ สูงสุดสามารถกำหนดได้:
โหมดสุ่ม 1, tileable:
ตัวเลือกเพิ่มเติม:
การเรนเดอร์ 512x512 ทั้งหมดสามารถพบได้ในโฟลเดอร์ดรอปบ็อกซ์ (* _64.png)
2048x1024 และ 4096x4096:
สิ่งเหล่านี้ใหญ่เกินกว่าจะฝังและโฮสต์รูปภาพทั้งหมดที่ฉันพบวางไว้ที่ 1600x1200 ขณะนี้ฉันกำลังแสดงผลชุดภาพขนาด 4096x4096 ภาพจะมีอีกมากในไม่ช้า แทนที่จะรวมลิงค์ทั้งหมดไว้ที่นี่เพียงลองดูที่ลิงค์เหล่านั้นในโฟลเดอร์ดรอปบ็อกซ์ (* _128.png และ * _256.png หมายเหตุ: 4096x4096 นั้นใหญ่เกินไปสำหรับผู้ดูตัวอย่างดรอปบ็อกซ์เพียงคลิก "ดาวน์โหลด") นี่คือบางส่วนของรายการโปรดของฉันคือ:
2048x1024 เพชรเรียงต่อกันขนาดใหญ่ (อันเดียวกับที่ฉันลิงค์ไว้ตอนเริ่มโพสต์นี้)
เพชร 2048x1024 (ฉันชอบอันนี้!) ลดขนาด:
4096x4096 เพชรเรียงต่อกันขนาดใหญ่ (ในที่สุด! คลิก 'ดาวน์โหลด' ในลิงก์ Dropbox มันใหญ่เกินไปสำหรับผู้ดูตัวอย่าง) ปรับขนาดลง:
โหมดสุ่ม 4096x4096 1 :
4096x4096 อีกหนึ่งที่ยอดเยี่ยม
อัปเดต: ชุดภาพที่ตั้งไว้ล่วงหน้า 2048x1024 เสร็จสิ้นและอยู่ในดรอปบ็อกซ์ ควรตั้งค่า 4096x4096 ภายในหนึ่งชั่วโมง
มีของดี ๆ มากมายฉันมีช่วงเวลาที่ยากมากที่จะเลือกคนที่จะโพสต์ดังนั้นโปรดตรวจสอบลิงก์ของโฟลเดอร์!