Java - Recursive Path
I start from a 2x3 closed path.
I scan each cell of the path and divide it into a new 3x3 sub-path. I try each time to choose the 3x3 sub-path that "looks like" the original picture.
I repeat the above process 4 times.
Here is the code:
package divide;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.imageio.ImageIO;
import snake.Image;
public class Divide {
private final static int MULT = 3;
private final static int ITERATIONS = 4;
public static void main(String[] args) throws IOException {
BufferedImage src = ImageIO.read(Image.class.getClassLoader().getResourceAsStream("input.png"));
BufferedImage dest = new BufferedImage(src.getWidth() * MULT, src.getHeight() * MULT, BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < src.getHeight() * MULT; y++) {
for (int x = 0; x < src.getWidth() * MULT; x++) {
dest.getRaster().setPixel(x, y, new int [] {255, 255, 255});
}
}
List<String> tab = new ArrayList<String>();
tab.add("rg");
tab.add("||");
tab.add("LJ");
for (int k = 1; k <= ITERATIONS; k++) {
boolean choose = k>=ITERATIONS-1;
// multiply size by 3
tab = iterate(src, tab, choose);
// fill in the white space - if needed
expand(src, tab, " r", " L", "r-", "L-", choose);
expand(src, tab, "g ", "J ", "-g", "-J", choose);
expand(src, tab, "LJ", " ", "||", "LJ", choose);
expand(src, tab, " ", "rg", "rg", "||", choose);
expand(src, tab, "L-J", " ", "| |", "L-J", choose);
expand(src, tab, " ", "r-g", "r-g", "| |", choose);
expand(src, tab, "| |", "| |", "Lg|", "rJ|", choose);
expand(src, tab, "--", " ", "gr", "LJ", choose);
expand(src, tab, " ", "--", "rg", "JL", choose);
expand(src, tab, "| ", "| ", "Lg", "rJ", choose);
expand(src, tab, " |", " |", "rJ", "Lg", choose);
for (String s : tab) {
System.out.println(s);
}
System.out.println();
}
for (int j = 0; j < tab.size(); j++) {
String line = tab.get(j);
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
int xleft = i * dest.getWidth() / line.length();
int xright = (i+1) * dest.getWidth() / line.length();
int ytop = j * dest.getHeight() / tab.size();
int ybottom = (j+1) * dest.getHeight() / tab.size();
int x = (xleft + xright) / 2;
int y = (ytop + ybottom) / 2;
if (c == '|') {
drawLine(dest, x, ytop, x, ybottom);
}
if (c == '-') {
drawLine(dest, xleft, y, xright, y);
}
if (c == 'L') {
drawLine(dest, x, y, xright, y);
drawLine(dest, x, y, x, ytop);
}
if (c == 'J') {
drawLine(dest, x, y, xleft, y);
drawLine(dest, x, y, x, ytop);
}
if (c == 'r') {
drawLine(dest, x, y, xright, y);
drawLine(dest, x, y, x, ybottom);
}
if (c == 'g') {
drawLine(dest, x, y, xleft, y);
drawLine(dest, x, y, x, ybottom);
}
}
}
ImageIO.write(dest, "png", new File("output.png"));
}
private static void drawLine(BufferedImage dest, int x1, int y1, int x2, int y2) {
int dist = Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2));
for (int i = 0; i <= dist; i++) {
int x = (x1*(dist - i) + x2 * i) / dist;
int y = (y1*(dist - i) + y2 * i) / dist;
dest.getRaster().setPixel(x, y, new int [] {0, 0, 0});
}
}
private static void expand(BufferedImage src, List<String> tab, String p1, String p2, String r1, String r2, boolean choose) {
for (int k = 0; k < (choose ? 2 : 1); k++) {
while (true) {
boolean again = false;
for (int j = 0; j < tab.size() - 1; j++) {
String line1 = tab.get(j);
String line2 = tab.get(j+1);
int baseScore = evaluateLine(src, j, tab.size(), line1) + evaluateLine(src, j+1, tab.size(), line2);
for (int i = 0; i <= line1.length() - p1.length(); i++) {
if (line1.substring(i, i + p1.length()).equals(p1)
&& line2.substring(i, i + p2.length()).equals(p2)) {
String nline1 = line1.substring(0, i) + r1 + line1.substring(i + p1.length());
String nline2 = line2.substring(0, i) + r2 + line2.substring(i + p2.length());
int nScore = evaluateLine(src, j, tab.size(), nline1) + evaluateLine(src, j+1, tab.size(), nline2);
if (!choose || nScore > baseScore) {
tab.set(j, nline1);
tab.set(j+1, nline2);
again = true;
break;
}
}
}
if (again) break;
}
if (!again) break;
}
String tmp1 = r1;
String tmp2 = r2;
r1 = p1;
r2 = p2;
p1 = tmp1;
p2 = tmp2;
}
}
private static int evaluateLine(BufferedImage src, int j, int tabSize, String line) {
int [] color = {0, 0, 0};
int score = 0;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
int x = i*src.getWidth() / line.length();
int y = j*src.getHeight() / tabSize;
src.getRaster().getPixel(x, y, color);
if (c == ' ' && color[0] >= 128) score++;
if (c != ' ' && color[0] < 128) score++;
}
return score;
}
private static List<String> iterate(BufferedImage src, List<String> tab, boolean choose) {
int [] color = {0, 0, 0};
List<String> tab2 = new ArrayList<String>();
for (int j = 0; j < tab.size(); j++) {
String line = tab.get(j);
String l1 = "", l2 = "", l3 = "";
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
List<String []> candidates = replace(c);
String [] choice = null;
if (choose) {
int best = 0;
for (String [] candidate : candidates) {
int bright1 = 0;
int bright2 = 0;
for (int j1 = 0; j1<3; j1++) {
int y = j*3+j1;
for (int i1 = 0; i1<3; i1++) {
int x = i*3+i1;
char c2 = candidate[j1].charAt(i1);
src.getRaster().getPixel(x*src.getWidth()/(line.length()*3), y*src.getHeight()/(tab.size()*3), color);
if (c2 != ' ') bright1++;
if (color[0] > 128) bright2++;
}
}
int score = Math.abs(bright1 - bright2);
if (choice == null || score > best) {
best = score;
choice = candidate;
}
}
} else {
choice = candidates.get(0);
}
//String [] r = candidates.get(rand.nextInt(candidates.size()));
String [] r = choice;
l1 += r[0];
l2 += r[1];
l3 += r[2];
}
tab2.add(l1);
tab2.add(l2);
tab2.add(l3);
}
return tab2;
}
private static List<String []> replace(char c) {
if (c == 'r') {
return Arrays.asList(
new String[] {
"r-g",
"| L",
"Lg "},
new String[] {
" ",
" r-",
" | "},
new String[] {
" ",
"r--",
"Lg "},
new String[] {
" rg",
" |L",
" | "},
new String[] {
" ",
" r",
" rJ"});
} else if (c == 'g') {
return Arrays.asList(
new String[] {
"r-g",
"J |",
" rJ"},
new String[] {
" ",
"-g ",
" | "},
new String[] {
" ",
"--g",
" rJ"},
new String[] {
"rg ",
"J| ",
" | "},
new String[] {
" ",
"g ",
"Lg "});
} else if (c == 'L') {
return Arrays.asList(
new String[] {
"rJ ",
"| r",
"L-J"},
new String[] {
" | ",
" L-",
" "},
new String[] {
"rJ ",
"L--",
" "},
new String[] {
" | ",
" |r",
" LJ"},
new String[] {
" Lg",
" L",
" "});
} else if (c == 'J') {
return Arrays.asList(
new String[] {
" Lg",
"g |",
"L-J"},
new String[] {
" | ",
"-J ",
" "},
new String[] {
" Lg",
"--J",
" "},
new String[] {
" | ",
"g| ",
"LJ "},
new String[] {
"rJ ",
"J ",
" "});
} else if (c == '-') {
return Arrays.asList(
new String[] {
" rg",
"g|L",
"LJ "},
new String[] {
"rg ",
"J|r",
" LJ"},
new String[] {
" ",
"---",
" "},
new String[] {
"r-g",
"J L",
" "},
new String[] {
" ",
"g r",
"L-J"},
new String[] {
"rg ",
"JL-",
" "},
new String[] {
" rg",
"-JL",
" "},
new String[] {
" ",
"gr-",
"LJ "},
new String[] {
" ",
"-gr",
" LJ"}
);
} else if (c == '|') {
return Arrays.asList(
new String[] {
" Lg",
"r-J",
"Lg "},
new String[] {
"rJ ",
"L-g",
" rJ"},
new String[] {
" | ",
" | ",
" | "},
new String[] {
" Lg",
" |",
" rJ"},
new String[] {
"rJ ",
"| ",
"Lg "},
new String[] {
" Lg",
" rJ",
" | "},
new String[] {
" | ",
" Lg",
" rJ"},
new String[] {
"rJ ",
"Lg ",
" | "},
new String[] {
" | ",
"rJ ",
"Lg "}
);
} else {
List<String []> ret = new ArrayList<String []>();
ret.add(
new String[] {
" ",
" ",
" "});
return ret;
}
}
}
2You might want to put some restriction on the relative resolutions. Otherwise one could just increase the resolution considerably (say a factor of 32 or something), and then replace each pixel with a 32x32 block of appropriate average intensity. It should be easy enough to make the blocks all connect and them arrange them in such a way that everything connects to a single loop. – Martin Ender – 2014-08-18T18:21:35.647
Does touching count as intersecting? – Calvin's Hobbies – 2014-08-18T18:28:01.803
@Calvin'sHobbies Yes I think the line should not touch itself. @ Martin Büttner Well that would be a perfectly fine solution if you first decrease the image to a low resolution! But what limits would you recommend? – flawr – 2014-08-18T18:54:43.627
1If the line can't touch itself, no dark areas, the darker shade will be a 50% gray – edc65 – 2014-08-18T19:26:43.757
@edc65 You can get it darker than that by making the line wider than a pixel, but yeah you won't get black. – Martin Ender – 2014-08-18T19:58:25.447
1@Martin
The width of the line shall be constant throughout the whole image.
But still a useful hint – edc65 – 2014-08-18T20:40:21.3172@edc65 Yes constant, but you can still make it wider than a pixel (constantly) in which case you can have two parts of the line separated by one pixel and then that area will be darker than 50% average intensity. – Martin Ender – 2014-08-18T20:47:49.943
Does no touching include no diagonal touching at corners? That is, can the line pass through (x,y) and later pass through (x+1,y+1)? – trichoplax – 2014-08-18T22:57:34.983
Answers to this question might be very useful inspiration for use in answers to the black and white forest question.
– trichoplax – 2014-08-18T23:00:04.060The example line image contains anti-aliasing (greyscale to smooth the edges of the line). Is this permitted in answers or must the output images contain only 2 types of pixel, pure black and pure white? – trichoplax – 2014-08-19T00:11:30.867
2@githubphagocyte Primarly the image should be in black and white, but it does not matter if it contains anti aliasing effects. And you should try to avoid this situation of diagonally touching pixels, but again, if this happens only a few times in the image it will be ok, as long as you do not use it systematically. Thank you for the input. @ edc65: Yes I am aware of that, the goal is that the viewer can still identify one distinct line on the image (when zooming in). – flawr – 2014-08-19T07:34:16.487
1
I would like to see the curve shortening flow of the generated images
– DenDenDo – 2014-08-19T10:39:01.513@DenDenDo Is there a general algorithm for this task? I imagine that you e.g. try to locally reduce the curvature in each step, but I have no idea how to implment this. – flawr – 2014-08-19T11:50:20.777
@flawr If the curve has a parametric representation
(x(t); y(t))
(or is stored as an ordered set of points) then you can compute the tangent vector (basically velocity of a point moving along the curve)( x(t)'; y(t)')
and the curvature (acceleration)(x(t)''; y(t)'')
and then move each point in the direction of the acceleration vector. Optionally add some scaling to preserve the total curve length or enclosed area. (too bad the math tags dont work here) – DenDenDo – 2014-08-19T14:14:58.483Oh that isn't even as difficult as I imagined it. So if any of the submitters would provide an ordered list of the corner points of their curves, I'd give it a try! – flawr – 2014-08-19T15:12:51.697
So I made a script in matlab for doing so, and it works (Amazing, @DenDenDo thank you for showing us this great effect=) now I'd just need a list of the points. – flawr – 2014-08-19T17:46:21.857
As interesting as that collapsing GIF is, have you considered posting it with the frames in reverse order and a pause at the end when the image has come into focus...? – trichoplax – 2014-08-19T20:36:06.507
Haha, no, but thats a nice idea! Let's see what I can do. – flawr – 2014-08-20T07:09:34.647
The "Update" doesn't make any sense without reading the comments (which are after it in the page rendering order), and mentioning one answer in the question seems like a way of biasing votes towards that answer (which should be done by accepting it, if it's the best according to the scoring criteria, or by mentioning it in chat otherwise ;) ). Perhaps move it to a link in the comments? – Peter Taylor – 2014-08-20T08:46:07.070
I didn't even think so far, just had fun fiddling around=) This is the backwards animation as suggested by @githubphagocyte: http://i.stack.imgur.com/muMWd.gif
– flawr – 2014-08-20T09:38:46.323