Chế biến
Cập nhật! Hình ảnh 4096x4096!
Tôi đã hợp nhất bài đăng thứ hai của mình vào bài này bằng cách kết hợp hai chương trình lại với nhau.
Một bộ sưu tập đầy đủ các hình ảnh được chọn có thể được tìm thấy ở đây, trên Dropbox . (Lưu ý: DropBox không thể tạo bản xem trước cho hình ảnh 4096x4096; chỉ cần nhấp vào chúng sau đó nhấp vào "Tải xuống").
Nếu bạn chỉ nhìn vào một cái nhìn vào cái này (có thể điều chỉnh được)! Ở đây, nó được thu nhỏ lại (và nhiều hơn nữa bên dưới), 2048x1024 ban đầu:
Chương trình này hoạt động bằng cách đi bộ các đường dẫn từ các điểm được chọn ngẫu nhiên trong khối màu, sau đó vẽ chúng thành các đường dẫn được chọn ngẫu nhiên trong ảnh. Có rất nhiều khả năng. Các tùy chọn cấu hình là:
- Chiều dài tối đa của đường dẫn khối màu.
- Bước tối đa để vượt qua khối màu (giá trị lớn hơn gây ra phương sai lớn hơn nhưng giảm thiểu số lượng đường dẫn nhỏ về cuối khi mọi thứ trở nên chặt chẽ).
- Ốp lát hình ảnh.
- Hiện tại có hai chế độ đường dẫn hình ảnh:
- Chế độ 1 (chế độ của bài gốc này): Tìm một khối pixel không sử dụng trong ảnh và hiển thị cho khối đó. Các khối có thể được đặt ngẫu nhiên, hoặc được sắp xếp từ trái sang phải.
- Chế độ 2 (chế độ của bài đăng thứ hai mà tôi đã hợp nhất vào bài này): Chọn một điểm bắt đầu ngẫu nhiên trong ảnh và đi dọc theo một con đường qua các pixel không sử dụng; có thể đi bộ xung quanh các pixel được sử dụng. Tùy chọn cho chế độ này:
- Đặt hướng để đi bộ (trực giao, chéo hoặc cả hai).
- Có hay không thay đổi hướng (hiện tại theo chiều kim đồng hồ nhưng mã vẫn linh hoạt) sau mỗi bước hoặc chỉ thay đổi hướng khi gặp pixel bị chiếm dụng ..
- Tùy chọn xáo trộn thứ tự thay đổi hướng (thay vì theo chiều kim đồng hồ).
Nó hoạt động cho tất cả các kích thước lên đến 4096x4096.
Bản phác thảo xử lý hoàn chỉnh có thể được tìm thấy ở đây: Tracer.zip
Tôi đã dán tất cả các tệp trong cùng một khối mã bên dưới chỉ để tiết kiệm dung lượng (thậm chí tất cả trong một tệp, nó vẫn là một bản phác thảo hợp lệ). Nếu bạn muốn sử dụng một trong các cài đặt trước, hãy thay đổi chỉ mục trong gPreset
bài tập. Nếu bạn chạy nó trong Xử lý, bạn có thể nhấn r
trong khi nó đang chạy để tạo một hình ảnh mới.
- Cập nhật 1: Mã được tối ưu hóa để theo dõi màu / pixel không sử dụng đầu tiên và không tìm kiếm trên các pixel đã sử dụng; giảm thời gian tạo 2048x1024 từ 10-30 phút xuống còn khoảng 15 giây và 4096x4096 từ 1-3 giờ xuống còn khoảng 1 phút. Thả hộp nguồn và nguồn dưới đây cập nhật.
- Cập nhật 2: Đã sửa lỗi ngăn không cho hình ảnh 4096x4096 được tạo.
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;
}
}
Dưới đây là bộ ảnh 256x128 đầy đủ mà tôi thích:
Chế độ 1:
Yêu thích của tôi từ bộ ban đầu (max_path_length = 512, path_step = 2, ngẫu nhiên, được hiển thị 2x, liên kết 256x128 ):
Những người khác (hai bên trái được đặt hàng, bên phải hai ngẫu nhiên, giới hạn hai chiều dài trên cùng, hai bên dưới không giới hạn):
Điều này có thể được lát gạch:
Chế độ 2:
Những cái này có thể được lát gạch:
Lựa chọn 512x512:
Kim cương có thể điều chỉnh, yêu thích của tôi từ chế độ 2; bạn có thể thấy trong phần này cách các đường dẫn đi xung quanh các đối tượng hiện có:
Bước đường dẫn lớn hơn và độ dài đường dẫn tối đa, có thể điều chỉnh:
Chế độ ngẫu nhiên 1, có thể điều chỉnh:
Nhiều lựa chọn hơn:
Tất cả các kết xuất 512x512 có thể được tìm thấy trong thư mục dropbox (* _64.png).
2048x1024 và 4096x4096:
Chúng quá lớn để nhúng và tất cả các máy chủ hình ảnh tôi tìm thấy thả chúng xuống 1600x1200. Tôi hiện đang hiển thị một bộ hình ảnh 4096x4096 để sớm có nhiều hơn nữa. Thay vì bao gồm tất cả các liên kết ở đây, chỉ cần kiểm tra chúng trong thư mục dropbox (* _128.png và * _256.png, lưu ý: các liên kết 4096x4096 quá lớn đối với trình xem trước dropbox, chỉ cần nhấp vào "tải xuống"). Dưới đây là một số mục yêu thích của tôi:
2048x1024 viên kim cương có thể điều chỉnh lớn (cùng loại mà tôi đã liên kết ở đầu bài này)
2048x1024 kim cương (tôi thích cái này!), Thu nhỏ lại:
4096x4096 viên kim cương có thể điều chỉnh lớn (Cuối cùng! Nhấp vào 'tải xuống' trong liên kết Dropbox; nó quá lớn so với trình xem trước của chúng), thu nhỏ lại:
4096x4096 chế độ ngẫu nhiên 1 :
4096x4096 một cái khác tuyệt vời
Cập nhật: Bộ ảnh cài đặt sẵn 2048x1024 đã hoàn tất và trong hộp thả xuống. Bộ 4096x4096 nên được thực hiện trong vòng một giờ.
Có rất nhiều bài hay, tôi đang rất khó chọn bài nào để đăng, vì vậy hãy kiểm tra liên kết thư mục!