This program prints a circle in a Cartesian plane.
The inputs are: radius, coordinates of the center of the circle (cx,cy), and the character with which we want to print the circle.
If the points of the circle overlap with the axes, the points have priority. I wrote a condition for the printing of the axes in method drawCircle, but the image is distorted...
Something is eluding me... can someone help me to find my mistake?
Here is my entire program (the method that has issues is the last one, drawCircle):
public class Circle {
public static void main(String[] args) {
System.out.println(onCircle(1, 2, 3, 4, 5));
drawCircle(1, 3, 3, '*');
drawCircle(3, 3, 3, '*');
drawCircle(5, 10, 12, '*');
}
//Question 1A
public static boolean onCircle(int radius, int cx, int cy, int x, int y) {
//default answer is false, but if the
//inequality holds then it is set to true
boolean isDrawn = false;
if (Math.pow(radius,2)<=(Math.pow((x-cx),2)+Math.pow((y-cy),2))
&& (Math.pow((x-cx),2)+Math.pow((y-cy),2))<=(Math.pow(radius,2)+1)) {
isDrawn = true;
}
return isDrawn;
}
//Question 1B
public static void verifyInput(int radius, int cx, int cy) {
//if radius is negative, display error message
if (radius <= 0) {
throw new IllegalArgumentException(
"The radius of the circle must be a positive number.");
}
//if the center of the circle with radius 'radius' causes the circle
//to 'overflow' into other quadrants, display error message
if ((cx - radius) < 0 || (cy - radius) < 0) {
throw new IllegalArgumentException(
"the circle with requested parameters does not fit " +
"in the quadrant. Consider moving the center of the " +
"circle further from the axes.");
}
}
//Question 1C
public static void drawCircle(int radius, int cx, int cy, char symbol) {
verifyInput(radius, cx, cy);
//set the values for extension of the axes (aka how long are they)
int xMax = cx + radius + 1;
int yMax = cy + radius + 1;
for (int j = yMax; j >= 0; j--) {
for (int i = 0; i <= xMax; i++) {
//set of if-block to print the axes
if (i == 0 && j == 0) {
System.out.print('+');
} else if (i == 0) {
if (j == yMax) {
System.out.print('^');
}
if (j != yMax && onCircle(radius, cx, cy, i, j) == false) {
System.out.print('|');
}
} else if (j == 0) {
if (i == xMax) {
System.out.print('>');
}
if (i != xMax && onCircle(radius, cx, cy, i, j) == false) {
System.out.print('-');
}
}
//if block to print the circle
//verify for each coordinate (i,j)
//in the quadrant if they are on circle
//if =true print symbol, if =false print empty character
if (onCircle(radius, cx, cy, i, j) == true) {
System.out.print(symbol);
} else {
System.out.print(' ');
}
}
System.out.println();
}
}
}
Here is what I am getting:
false
^
| ***
| * *
| ***
|
+ - - - - >
^
| ***
|
* *
* *
* *
|
+ - ***- - >
^
| ***
| * *
| * *
|
| * *
| * *
| * *
|
| * *
| * *
| ***
|
|
|
|
|
|
+ - - - - - - - - - - - - - - - >
As you can see, the 1st and 3rd circles are fine, but the one that overlaps with the axes is distorted.
The general Equation of a circle centred at the origin:
In Java it can be implemented like this:
i*i + j*j == r*r
But in the case of an integer coordinate system, you have to round this equation somehow so that all the points of the circle are reflected in this coordinate system:
(int) Math.sqrt(i*i + j*j) == r
If r=8, then the circle and axes look like this:
r=8
* * * * * * * * *
* * * * *
* * *
* * *
* * * * *
* * *
* * *
* * *
* * * * * * * * * * * * * * * * *
* * *
* * *
* * *
* * * * *
* * *
* * *
* * * * *
* * * * * * * * *
Try it online!
int r = 8;
System.out.println("r=" + r);
IntStream.rangeClosed(-r, r)
.peek(i -> IntStream.rangeClosed(-r, r)
.mapToObj(j -> i == 0 || j == 0 ||
(int) Math.sqrt(i*i + j*j) == r ?
"* " : " ")
.forEach(System.out::print))
.forEach(i -> System.out.println());
See also: Print out an ASCII star in java
You're missing 3 continue statements.
Check out this revised version of your drawCircle method:
public static void drawCircle(int radius, int cx, int cy, char symbol) {
verifyInput(radius, cx, cy);
//set the values for extension of the axes (aka how long are they)
int xMax = cx + radius + 1;
int yMax = cy + radius + 1;
for (int j = yMax; j >= 0; j--) {
for (int i = 0; i <= xMax; i++) {
//set of if-block to print the axes
if (i == 0 && j == 0) {
System.out.print('+');
continue;
} else if (i == 0) {
if (j == yMax) {
System.out.print('^');
}
if (j != yMax && onCircle(radius, cx, cy, i, j) == false) {
System.out.print('|');
continue;
}
} else if (j == 0) {
if (i == xMax) {
System.out.print('>');
}
if (i != xMax && onCircle(radius, cx, cy, i, j) == false) {
System.out.print('-');
continue;
}
}
//if block to print the circle
//verify for each coordinate (i,j)
//in the quadrant if they are on circle
//if =true print symbol, if =false print empty character
if (onCircle(radius, cx, cy, i, j) == true) {
System.out.print(symbol);
} else {
System.out.print(' ');
}
}
System.out.println();
}
}
Actually when debugging, your onCircle methods gets for x=0 and y=4, cx=3, cy=3:
You have:
Math.pow(radius=3, 2) = 9
Math.pow((x - cx), 2) = 9
Math.pow((y - cy), 2) = 1
Hence
Math.pow(radius, 2) <= Math.pow((x - cx), 2) + Math.pow((y - cy), 2)
returns true
Then:
(Math.pow((x-cx),2) = 9
Math.pow((y-cy),2)) = 1
(Math.pow(radius,2)+1)) = 10
Thus
(Math.pow((x-cx),2)+Math.pow((y-cy),2)) <= (Math.pow(radius,2)+1))
returns also true
Thus onCircle(radius,cx,cy,i,j) returns true for this coordinate.
And that's why you get your symbol drawn. You need to improve your algorithm!
Related
I am making a software that has a screen filtering process. The code can already shift the colors and apply it to the frame the only problem is that It takes around 40-50 seconds for the filtered window to render. Is there any way I can make it faster? I would also like to ask which part of the code makes the process longer and time consuming. I will really appreciate the help. Thanks!
Here is my code.
public RedGreenFilter(int k1, int k2, int k3) {
this.k1 = k1;
this.k2 = k2;
this.k3 = k3;
}
#Override
public BufferedImage filter(final BufferedImage src, BufferedImage dst) {
if (dst == null) {
dst = this.createCompatibleDestImage(src, null);
}
// make sure the two images have the same size, color space, etc.
// MISSING !!! ???
DataBufferInt inBuffer = (DataBufferInt) src.getRaster().getDataBuffer();
DataBufferInt outBuffer = (DataBufferInt) dst.getRaster().getDataBuffer();
int[] inData = inBuffer.getData();
int[] outData = outBuffer.getData();
int simR = 0, simG = 0, simB = 0, mild = 10, moderate = 20, strong = 40;
float cIndex = MyDeficiency.cIndex;
float angle = MyDeficiency.angle;
float x = 0, y = 0, z = 0;
int prevIn = 0;
int prevOut = 0;
final int length = inData.length;
int corrG;
int corrR;
int corrB;
int SizeOfScreen = src.getHeight() * src.getWidth();
final int[] redArray = new int[SizeOfScreen];
final int[] blueArray = new int[SizeOfScreen];
final int[] greenArray = new int[SizeOfScreen];
for (int i = 0; i < SizeOfScreen; i++) {
final int in = inData[i];
if (in == prevIn) {
outData[i] = prevOut;
} else {
final int r = (0xff0000 & in) >> 16;
final int g = (0xff00 & in) >> 8;
final int b = 0xff & in;
// get linear rgb values in the range 0..2^15-1
final int r_lin = rgb2lin_red_LUT[r];
final int g_lin = rgb2lin_red_LUT[g];
final int b_lin = rgb2lin_red_LUT[b];
//http://publication.gunadarma.ac.id/bitstream/123456789/12232/1/Slide_Mahendra_54411250.pdf
float L = (17.8824f * r + 43.5161f * g + 4.11935f * b);
float M = (3.4565f * r + 27.1554f * g + 3.86714f * b);
float S = (0.02996f * r + 0.18431f * g + 1.46709f * b);
float dL = ( 0 * L + 2.02344f * M - 2.52581f * S);
float dM = ( 0 * L + 1 * M + 0 * S);
float dS = ( 0 * L + 0 * M + 1 * S);
float pL = ( 1 * L + 0 * M + 0 * S);
float pM = ( 0.494207f * L + 0 * M + 1.24827f * S);
float pS = ( 0 * L + 0 * M + 1 * S);
// simulated red and green are identical
// scale the matrix values to 0..2^15 for integer computations
// of the simulated protan values.
// divide after the computation by 2^15 to rescale.
// also divide by 2^15 asnd multiply by 2^8 to scale the linear rgb to 0..255
// total division is by 2^15 * 2^15 / 2^8 = 2^22
// shift the bits by 22 places instead of dividing
int r_blind = (int) (k1 * r_lin + k2 * g_lin) >> 22;
int b_blind = (int) (k3 * r_lin - k3 * g_lin + 32768 * b_lin) >> 22;
if (r_blind < 0) {
r_blind = 0;
} else if (r_blind > 255) {
r_blind = 255;
}
if (b_blind < 0) {
b_blind = 0;
} else if (b_blind > 255) {
b_blind = 255;
}
// convert reduced linear rgb to gamma corrected rgb
int red = lin2rgb_LUT[r_blind];
red = red >= 0 ? red : 256 + red; // from unsigned to signed
int blue = lin2rgb_LUT[b_blind];
blue = blue >= 0 ? blue : 256 + blue; // from unsigned to signed
if(k1 == 9591 && k2 == 23173){
x = dL;
y = dM;
z = dS;
}
if(k1 == 3683 && k2 == 29084){
x = pL;
y = pM;
z = pS;
}
//SEVERITY
//PROTAN
//normal to mild
if (angle > 0.70 && cIndex < 1.2){
simR = (int)(0.0809533970341018f * x -0.1305188419612954f * y + 0.11673398252989027f * z);
simG = (int)(-0.01025222049871863f * x + 0.054025275314902886f * y -0.11362003603724172f * z);
simB = (int)(-0.0003651971010795924f * x -0.004121801653701777f * y + 0.693511617368688f * z);
}
//mild to moderate
else if (angle > 0.70 && cIndex < 3 && cIndex > 1.20){
simR = (int)(0.0809533970341018f * x -0.1305188419612954f * y + 0.11673398252989027f * z) + mild;
simG = (int)(-0.01025222049871863f * x + 0.054025275314902886f * y -0.11362003603724172f * z);
simB = (int)(-0.0003651971010795924f * x -0.004121801653701777f * y + 0.693511617368688f * z);
}
//moderate to strong
else if (angle > 0.70 && cIndex < 4 && cIndex > 3){
simR = (int)(0.0809533970341018f * x -0.1305188419612954f * y + 0.11673398252989027f * z) + moderate;
simG = (int)(-0.01025222049871863f * x + 0.054025275314902886f * y -0.11362003603724172f * z);
simB = (int)(-0.0003651971010795924f * x -0.004121801653701777f * y + 0.693511617368688f * z);
}
//strong to super strong
else if (angle > 0.70 && cIndex > 4){
simR = (int)(0.0809533970341018f * x -0.1305188419612954f * y + 0.11673398252989027f * z) + strong;
simG = (int)(-0.01025222049871863f * x + 0.054025275314902886f * y -0.11362003603724172f * z);
simB = (int)(-0.0003651971010795924f * x -0.004121801653701777f * y + 0.693511617368688f * z);
}
//DEUTAN
//normal to mild
if (angle < 0.70 && angle > -65.00 && cIndex < 1.2){
simR = (int)(0.0809533970341018f * x -0.1305188419612954f * y + 0.11673398252989027f * z);
simG = (int)(-0.01025222049871863f * x + 0.054025275314902886f * y -0.11362003603724172f * z);
simB = (int)(-0.0003651971010795924f * x -0.004121801653701777f * y + 0.693511617368688f * z);
}
//mild to moderate
else if (angle < 0.70 && angle > -65.00 && cIndex < 3 && cIndex > 1.20){
simR = (int)(0.0809533970341018f * x -0.1305188419612954f * y + 0.11673398252989027f * z);
simG = (int)(-0.01025222049871863f * x + 0.054025275314902886f * y -0.11362003603724172f * z) + mild;
simB = (int)(-0.0003651971010795924f * x -0.004121801653701777f * y + 0.693511617368688f * z);
}
//moderate to strong
else if (angle < 0.70 && angle > -65 && cIndex < 4 && cIndex > 3){
simR = (int)(0.0809533970341018f * x -0.1305188419612954f * y + 0.11673398252989027f * z);
simG = (int)(-0.01025222049871863f * x + 0.054025275314902886f * y -0.11362003603724172f * z) + moderate;
simB = (int)(-0.0003651971010795924f * x -0.004121801653701777f * y + 0.693511617368688f * z) ;
}
//strong to super strong
else if (angle < 0.7 && angle > -65 && cIndex > 4){
simR = (int)(0.0809533970341018f * x -0.1305188419612954f * y + 0.11673398252989027f * z);
simG = (int)(-0.01025222049871863f * x + 0.054025275314902886f * y -0.11362003603724172f * z) + strong;
simB = (int)(-0.0003651971010795924f * x -0.004121801653701777f * y + 0.693511617368688f * z) ;
}
int errR = r - simR;
int errG = g - simG;
int errB = b - simB;
float errModR = (0 * errR + 0 * errG + 0 * errB);
float errModG = (0.7f * errR + 1 * errG + 0 * errB);
float errModB = (0.7f * errR + 0 * errG + 1 * errB);
corrR = (int) errModR + r;
corrG = (int) errModG + g;
corrB = (int) errModB + b;
/*
int corrR = 0;
int corrG = 0;
int corrB = 0;
*/
//MyDeficiency my = new MyDeficiency();
if(r == 255 && g == 255 && b == 255){
corrR = corrG = corrB = 255;
}
if(r == 0 && g == 0 && b == 0){
corrR = corrG = corrB = 0;
}
final int out = 0xff000000 | (corrR << 16) | (corrG << 8) | corrB ;
redArray[i] = (out >> 16) & 0xFF;
greenArray[i] = (out >> 8) & 0xFF;
blueArray[i] = (out >> 0) & 0xFF;
//System.out.println("r: " +" " + redArray[i]+ " "+ " g:" +" " +greenArray[i] +" " +"b:"+" "+ blueArray[i] + " " +"i:"+ " "+i);
//System.out.println("sR: " + (red << 16) + "sG: " +(red <<8)+ " sB: " + blue);
//outData[i] = out;
//prevIn = in;
//prevOut = out;
}
}
//System.out.println(SizeOfScreen);
Window w = new Window(null);
w.add(new JComponent() {
public void paintComponent(Graphics g) {
int alpha = 190; // 50% transparent
int pixelcounter = 0;
for(int i = 0; i < src.getHeight(); i++){
for(int j = 0; j < src.getWidth(); j++){
Color myColour = new Color(redArray[pixelcounter],greenArray[pixelcounter],blueArray[pixelcounter], alpha);
g.setColor(myColour);
g.fillRect(j,i,1,1);
pixelcounter++;
}
}
}
public Dimension getPreferredSize() {
return new Dimension(src.getWidth(), src.getHeight());
}
});
w.pack();
w.setLocationRelativeTo(null);
w.setAlwaysOnTop(true);
/**
* This sets the background of the window to be transparent.
*/
AWTUtilities.setWindowOpaque(w, false);
setTransparent(w);
w.setVisible(true);
//w.setVisible(false);
return dst;
}
}
I have to print a circle (taking as input it's radius, the coordinates of the center of the circle (cx and cy), and the character with which it has to be drawn).
I wrote a series of if-blocks for the axes and the circle. They work well if I use them separately, but when I put them in the same method (I have to have only one method) they overlap in an undesirable way.
Note: in case if the character overlaps the axes, the character has priority.
public static void drawCircle(int radius, int cx, int cy, char symbol) {
// this method verifies that there are no negative
// values involved (throws error/exception)
verifyInput(radius, cx, cy);
// set the values for extension of the axes
// (aka how long are they)
int xMax = cx + radius + 1;
int yMax = cy + radius + 1;
for (int j = yMax; j >= 0; j--) {
for (int i = 0; i <= xMax; i++) {
// set of if-block to print the axes
if (i == 0 && j == 0) {
System.out.print('+');
} else if (i == 0) {
if (j == yMax) {
System.out.print('^');
}
if (j != yMax) {
System.out.println('|');
}
} else if (j == 0) {
if (i == xMax) {
System.out.println('>');
}
if (i != xMax) {
System.out.print('-');
}
}
// if block to print the circle
if (onCircle(radius, cx, cy, i, j) == true) {
System.out.print('*');
} else {
System.out.print(' ');
}
}
// I'm not sure if I need to use the print here V
// System.out.println()
}
}
Here is the onCircle method. It verifies for every i,j if it is on the outline of the circle to be drawn.
public static boolean onCircle(int radius, int cx, int cy, int x, int y) {
boolean isDrawn = false;
if (Math.pow(radius,2)<=(Math.pow((x-cx),2)+Math.pow((y-cy),2))
&& (Math.pow((x-cx),2)+Math.pow((y-cy),2))<=(Math.pow(radius,2)+1)) {
isDrawn = true;
}
return isDrawn;
}
Here is my output (without the last print statement):
^ |
*** |
* * |
* * |
* * + - - - - -*-*-*- >
Here is my output (with the last print statement):
^
|
***
|
* *
|
* *
|
* *
+ - - - - -*-*-*- >
Here is what I should get:
The general Equation of a circle:
In Java it can be implemented like this:
(x-a)*(x-a) + (y-b)*(y-b) == r*r
In the case of an integer coordinate system, it can be rounded like this:
(int) Math.sqrt((x-a)*(x-a) + (y-b)*(y-b)) == r
If the radius r=12 and the center of the circle a=5,b=1, then the circle and axes look like this:
r=12,a=5,b=1
^y
|
|
| * * * * * * * * *
* * * *
* * | * *
* * | * *
* * | * *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | * *
------* ------------+---------------------------------* ---->x
* | *
* | *
* | *
* | *
* | *
* | *
* * | * *
* * | * *
* * | * *
* * * *
| * * * * * * * * *
|
|
|
Try it online!
// radius
int r = 12;
// center of the circle
int a = 5, b = 1;
// whitespace
int s = 3;
// print area
int xMin = a-r-s, xMax = a+r+s;
int yMin = b-r-s, yMax = b+r+s;
// output an ASCII circle and axes
System.out.println("r="+r+",a="+a+",b="+b);
for (int y = yMax; y >= yMin; y--) {
for (int x = xMin; x <= xMax; x++) {
if ((int) Math.sqrt((x-a)*(x-a) + (y-b)*(y-b)) == r) {
// circle
System.out.print("* ");
} else if (x == a && y == b) {
// center of the circle
System.out.print("* ");
} else if (x == 0 && y == 0) {
// origin of coordinates
System.out.print("+-");
} else if (x == 0) {
// ordinate axis - y
System.out.print(y == yMax ? "^y" : "| ");
} else if (y == 0) {
// abscissa axis - x
System.out.print(x == xMax ? ">x" : "--");
} else {
// whitespace
System.out.print(" ");
}
}
// new line
System.out.println();
}
See also:
• Print out an ASCII circle and axes
• Print out an ASCII circle of the specified width
It looks like the formatting issue is that you are sometimes using System.out.println where you should be using System.out.print. System.out.println will end the current line, so it should only be used once per output line. Thus you should just have the single empty System.out.printlnstatement at the end of the outer (j) loop, and use System.out.print throughout the rest of the program.
You will also need to fix the printing of the horizontal axis, as at the moment it will print both the axis and the circle, instead of just the circle.
jwaddell is right, your code needs only 5 minor updates like this (see comment like // <--):
public static void drawCircle(int radius, int cx, int cy, char symbol) {
// this method verifies that there are no negative
// values involved (throws error/exception)
verifyInput(radius, cx, cy);
// set the values for extension of the axes
// (aka how long are they)
int xMax = cx + radius + 1;
int yMax = cy + radius + 1;
for (int j = yMax; j >= 0; j--) {
for (int i = 0; i <= xMax; i++) {
// set of if-block to print the axes
if (onCircle(radius, cx, cy, i, j) == true) { // <-- draw circle first
System.out.print(symbol); // <-- use param 'symbol' here
} else if (i == 0 && j == 0) {
System.out.print('+');
} else if (i == 0) {
if (j == yMax) {
System.out.print('^');
}
if (j != yMax) {
System.out.print('|'); // <-- don't print new line here
}
} else if (j == 0) {
if (i == xMax) {
System.out.print('>'); // <-- don't print new line here
}
if (i != xMax) {
System.out.print('-');
}
} else {
System.out.print(' ');
}
}
System.out.println(); // <-- then add new line here
}
}
You can optimize onCircle like this:
public static boolean onCircle(int radius, int cx, int cy, int x, int y) {
double distance2 = Math.pow((x - cx), 2) + Math.pow((y - cy), 2);
double radius2 = Math.pow(radius, 2);
return radius2 <= distance2 && distance2 <= (radius2 + 1.0d);
}
The result with drawCircle(5, 10, 12, '&'); is as expected:
^
| &&&
| & &
| & &
|
| & &
| & &
| & &
|
| & &
| & &
| &&&
|
|
|
|
|
|
+--------------->
i have been working on a java game engine but my render keeps getting the unreachable code error.The error appears at the setPixels method.
public class Renderer {
private int width, height;
private byte[] pixels;
public Renderer(GameContainer gc){
width = gc.getWidth();
height = gc.getHeight();
pixels = ((DataBufferByte)gc.getWindow().getImage().getRaster().getDataBuffer()).getData();
}
public void setPixel(int x, int y, float a, float r, float g, float b){
if((x < 0 || x>= width || y < 0 || y>= height) || a == 0){
return;
int index = (x + y * width) * 4;
pixels[index] = (byte)((a * 255f) + 0.5f);
pixels[index + 1] = (byte)((b * 255f) + 0.5f);
pixels[index + 2] = (byte)((g * 255f) + 0.5f);
pixels[index + 3] = (byte)((r * 255f) + 0.5f);
}
}
public void clear(){
for(int x = 0; x < width; x++){
for(int y = 0; y < height; y++){
setPixel(x,y,1,0,1,1);
}
}
}
}
I think this is what you are trying to do?
Your if statement should not be enclosing all the statements in your function.
public void setPixel(int x, int y, float a, float r, float g, float b){
// Check for invalid values
if((x < 0 || x>= width || y < 0 || y>= height) || a == 0){
// Break out of function if invalid values detected
return;
}
// Update pixel
int index = (x + y * width) * 4;
pixels[index] = (byte)((a * 255f) + 0.5f);
pixels[index + 1] = (byte)((b * 255f) + 0.5f);
pixels[index + 2] = (byte)((g * 255f) + 0.5f);
pixels[index + 3] = (byte)((r * 255f) + 0.5f);
}
The return statement ends the execution of a method. In the event that the if statement is run in the code below, the method will hit the return and end before doing all the other stuff. You don't seem to need a return statement in setPixel since there isn't a need to end the method prematurely.
public void setPixel(int x, int y, float a, float r, float g, float b) {
if((x < 0 || x>= width || y < 0 || y>= height) || a == 0){
//return;
int index = (x + y * width) * 4;
pixels[index] = (byte)((a * 255f) + 0.5f);
pixels[index + 1] = (byte)((b * 255f) + 0.5f);
pixels[index + 2] = (byte)((g * 255f) + 0.5f);
pixels[index + 3] = (byte)((r * 255f) + 0.5f);
}
}
Hi I am working a on project that I need to implement an edge detector. I need to do it in VHDL however I am a little better at Java so I am looking at getting a working code in Java first then transfering it over. The code below I found but I can't get it working, I keep getting an error in the main on this line: detector.setSourceImage(frame); error says frame can not be resolved to a variable. I understand why I'm getting the error but not sure how to fix it because I don't know how to get the picture in. I am just looking for a quick fix to make this work so I can get started on the VHDL part. Thanks for any help you can give.
package CannyEdgeDetector;
public class CannyEdgeDetector {
// statics
private final static float GAUSSIAN_CUT_OFF = 0.005f;
private final static float MAGNITUDE_SCALE = 100F;
private final static float MAGNITUDE_LIMIT = 1000F;
private final static int MAGNITUDE_MAX = (int) (MAGNITUDE_SCALE * MAGNITUDE_LIMIT);
// fields
private int height;
private int width;
private int picsize;
private int[] data;
private int[] magnitude;
private BufferedImage sourceImage;
private BufferedImage edgesImage;
private float gaussianKernelRadius;
private float lowThreshold;
private float highThreshold;
private int gaussianKernelWidth;
private boolean contrastNormalized;
private float[] xConv;
private float[] yConv;
private float[] xGradient;
private float[] yGradient;
// constructors
/**
* Constructs a new detector with default parameters.
*/
public CannyEdgeDetector() {
lowThreshold = 2.5f;
highThreshold = 7.5f;
gaussianKernelRadius = 2f;
gaussianKernelWidth = 16;
contrastNormalized = false;
}
// accessors
/**
* The image that provides the luminance data used by this detector to
* generate edges.
*
* #return the source image, or null
*/
public BufferedImage getSourceImage() {
return sourceImage;
}
/**
* Specifies the image that will provide the luminance data in which edges
* will be detected. A source image must be set before the process method
* is called.
*
* #param image a source of luminance data
*/
public void setSourceImage(BufferedImage image) {
sourceImage = image;
}
/**
* Obtains an image containing the edges detected during the last call to
* the process method. The buffered image is an opaque image of type
* BufferedImage.TYPE_INT_ARGB in which edge pixels are white and all other
* pixels are black.
*
* #return an image containing the detected edges, or null if the process
* method has not yet been called.
*/
public BufferedImage getEdgesImage() {
return edgesImage;
}
/**
* Sets the edges image. Calling this method will not change the operation
* of the edge detector in any way. It is intended to provide a means by
* which the memory referenced by the detector object may be reduced.
*
* #param edgesImage expected (though not required) to be null
*/
public void setEdgesImage(BufferedImage edgesImage) {
this.edgesImage = edgesImage;
}
/**
* The low threshold for hysteresis. The default value is 2.5.
*
* #return the low hysteresis threshold
*/
public float getLowThreshold() {
return lowThreshold;
}
/**
* Sets the low threshold for hysteresis. Suitable values for this parameter
* must be determined experimentally for each application. It is nonsensical
* (though not prohibited) for this value to exceed the high threshold value.
*
* #param threshold a low hysteresis threshold
*/
public void setLowThreshold(float threshold) {
if (threshold < 0) throw new IllegalArgumentException();
lowThreshold = threshold;
}
/**
* The high threshold for hysteresis. The default value is 7.5.
*
* #return the high hysteresis threshold
*/
public float getHighThreshold() {
return highThreshold;
}
/**
* Sets the high threshold for hysteresis. Suitable values for this
* parameter must be determined experimentally for each application. It is
* nonsensical (though not prohibited) for this value to be less than the
* low threshold value.
*
* #param threshold a high hysteresis threshold
*/
public void setHighThreshold(float threshold) {
if (threshold < 0) throw new IllegalArgumentException();
highThreshold = threshold;
}
/**
* The number of pixels across which the Gaussian kernel is applied.
* The default value is 16.
*
* #return the radius of the convolution operation in pixels
*/
public int getGaussianKernelWidth() {
return gaussianKernelWidth;
}
/**
* The number of pixels across which the Gaussian kernel is applied.
* This implementation will reduce the radius if the contribution of pixel
* values is deemed negligable, so this is actually a maximum radius.
*
* #param gaussianKernelWidth a radius for the convolution operation in
* pixels, at least 2.
*/
public void setGaussianKernelWidth(int gaussianKernelWidth) {
if (gaussianKernelWidth < 2) throw new IllegalArgumentException();
this.gaussianKernelWidth = gaussianKernelWidth;
}
/**
* The radius of the Gaussian convolution kernel used to smooth the source
* image prior to gradient calculation. The default value is 16.
*
* #return the Gaussian kernel radius in pixels
*/
public float getGaussianKernelRadius() {
return gaussianKernelRadius;
}
/**
* Sets the radius of the Gaussian convolution kernel used to smooth the
* source image prior to gradient calculation.
*
* #return a Gaussian kernel radius in pixels, must exceed 0.1f.
*/
public void setGaussianKernelRadius(float gaussianKernelRadius) {
if (gaussianKernelRadius < 0.1f) throw new IllegalArgumentException();
this.gaussianKernelRadius = gaussianKernelRadius;
}
/**
* Whether the luminance data extracted from the source image is normalized
* by linearizing its histogram prior to edge extraction. The default value
* is false.
*
* #return whether the contrast is normalized
*/
public boolean isContrastNormalized() {
return contrastNormalized;
}
/**
* Sets whether the contrast is normalized
* #param contrastNormalized true if the contrast should be normalized,
* false otherwise
*/
public void setContrastNormalized(boolean contrastNormalized) {
this.contrastNormalized = contrastNormalized;
}
// methods
public void process() {
width = sourceImage.getWidth();
height = sourceImage.getHeight();
picsize = width * height;
initArrays();
readLuminance();
if (contrastNormalized) normalizeContrast();
computeGradients(gaussianKernelRadius, gaussianKernelWidth);
int low = Math.round(lowThreshold * MAGNITUDE_SCALE);
int high = Math.round( highThreshold * MAGNITUDE_SCALE);
performHysteresis(low, high);
thresholdEdges();
writeEdges(data);
}
// private utility methods
private void initArrays() {
if (data == null || picsize != data.length) {
data = new int[picsize];
magnitude = new int[picsize];
xConv = new float[picsize];
yConv = new float[picsize];
xGradient = new float[picsize];
yGradient = new float[picsize];
}
}
//NOTE: The elements of the method below (specifically the technique for
//non-maximal suppression and the technique for gradient computation)
//are derived from an implementation posted in the following forum (with the
//clear intent of others using the code):
// http://forum.java.sun.com/thread.jspa?threadID=546211&start=45&tstart=0
//My code effectively mimics the algorithm exhibited above.
//Since I don't know the providence of the code that was posted it is a
//possibility (though I think a very remote one) that this code violates
//someone's intellectual property rights. If this concerns you feel free to
//contact me for an alternative, though less efficient, implementation.
private void computeGradients(float kernelRadius, int kernelWidth) {
//generate the gaussian convolution masks
float kernel[] = new float[kernelWidth];
float diffKernel[] = new float[kernelWidth];
int kwidth;
for (kwidth = 0; kwidth < kernelWidth; kwidth++) {
float g1 = gaussian(kwidth, kernelRadius);
if (g1 <= GAUSSIAN_CUT_OFF && kwidth >= 2) break;
float g2 = gaussian(kwidth - 0.5f, kernelRadius);
float g3 = gaussian(kwidth + 0.5f, kernelRadius);
kernel[kwidth] = (g1 + g2 + g3) / 3f / (2f * (float) Math.PI * kernelRadius * kernelRadius);
diffKernel[kwidth] = g3 - g2;
}
int initX = kwidth - 1;
int maxX = width - (kwidth - 1);
int initY = width * (kwidth - 1);
int maxY = width * (height - (kwidth - 1));
//perform convolution in x and y directions
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
int index = x + y;
float sumX = data[index] * kernel[0];
float sumY = sumX;
int xOffset = 1;
int yOffset = width;
for(; xOffset < kwidth ;) {
sumY += kernel[xOffset] * (data[index - yOffset] + data[index + yOffset]);
sumX += kernel[xOffset] * (data[index - xOffset] + data[index + xOffset]);
yOffset += width;
xOffset++;
}
yConv[index] = sumY;
xConv[index] = sumX;
}
}
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
float sum = 0f;
int index = x + y;
for (int i = 1; i < kwidth; i++)
sum += diffKernel[i] * (yConv[index - i] - yConv[index + i]);
xGradient[index] = sum;
}
}
for (int x = kwidth; x < width - kwidth; x++) {
for (int y = initY; y < maxY; y += width) {
float sum = 0.0f;
int index = x + y;
int yOffset = width;
for (int i = 1; i < kwidth; i++) {
sum += diffKernel[i] * (xConv[index - yOffset] - xConv[index + yOffset]);
yOffset += width;
}
yGradient[index] = sum;
}
}
initX = kwidth;
maxX = width - kwidth;
initY = width * kwidth;
maxY = width * (height - kwidth);
for (int x = initX; x < maxX; x++) {
for (int y = initY; y < maxY; y += width) {
int index = x + y;
int indexN = index - width;
int indexS = index + width;
int indexW = index - 1;
int indexE = index + 1;
int indexNW = indexN - 1;
int indexNE = indexN + 1;
int indexSW = indexS - 1;
int indexSE = indexS + 1;
float xGrad = xGradient[index];
float yGrad = yGradient[index];
float gradMag = hypot(xGrad, yGrad);
//perform non-maximal supression
float nMag = hypot(xGradient[indexN], yGradient[indexN]);
float sMag = hypot(xGradient[indexS], yGradient[indexS]);
float wMag = hypot(xGradient[indexW], yGradient[indexW]);
float eMag = hypot(xGradient[indexE], yGradient[indexE]);
float neMag = hypot(xGradient[indexNE], yGradient[indexNE]);
float seMag = hypot(xGradient[indexSE], yGradient[indexSE]);
float swMag = hypot(xGradient[indexSW], yGradient[indexSW]);
float nwMag = hypot(xGradient[indexNW], yGradient[indexNW]);
float tmp;
/*
* An explanation of what's happening here, for those who want
* to understand the source: This performs the "non-maximal
* supression" phase of the Canny edge detection in which we
* need to compare the gradient magnitude to that in the
* direction of the gradient; only if the value is a local
* maximum do we consider the point as an edge candidate.
*
* We need to break the comparison into a number of different
* cases depending on the gradient direction so that the
* appropriate values can be used. To avoid computing the
* gradient direction, we use two simple comparisons: first we
* check that the partial derivatives have the same sign (1)
* and then we check which is larger (2). As a consequence, we
* have reduced the problem to one of four identical cases that
* each test the central gradient magnitude against the values at
* two points with 'identical support'; what this means is that
* the geometry required to accurately interpolate the magnitude
* of gradient function at those points has an identical
* geometry (upto right-angled-rotation/reflection).
*
* When comparing the central gradient to the two interpolated
* values, we avoid performing any divisions by multiplying both
* sides of each inequality by the greater of the two partial
* derivatives. The common comparand is stored in a temporary
* variable (3) and reused in the mirror case (4).
*
*/
if (xGrad * yGrad <= (float) 0 /*(1)*/
? Math.abs(xGrad) >= Math.abs(yGrad) /*(2)*/
? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * neMag - (xGrad + yGrad) * eMag) /*(3)*/
&& tmp > Math.abs(yGrad * swMag - (xGrad + yGrad) * wMag) /*(4)*/
: (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * neMag - (yGrad + xGrad) * nMag) /*(3)*/
&& tmp > Math.abs(xGrad * swMag - (yGrad + xGrad) * sMag) /*(4)*/
: Math.abs(xGrad) >= Math.abs(yGrad) /*(2)*/
? (tmp = Math.abs(xGrad * gradMag)) >= Math.abs(yGrad * seMag + (xGrad - yGrad) * eMag) /*(3)*/
&& tmp > Math.abs(yGrad * nwMag + (xGrad - yGrad) * wMag) /*(4)*/
: (tmp = Math.abs(yGrad * gradMag)) >= Math.abs(xGrad * seMag + (yGrad - xGrad) * sMag) /*(3)*/
&& tmp > Math.abs(xGrad * nwMag + (yGrad - xGrad) * nMag) /*(4)*/
) {
magnitude[index] = gradMag >= MAGNITUDE_LIMIT ? MAGNITUDE_MAX : (int) (MAGNITUDE_SCALE * gradMag);
//NOTE: The orientation of the edge is not employed by this
//implementation. It is a simple matter to compute it at
//this point as: Math.atan2(yGrad, xGrad);
} else {
magnitude[index] = 0;
}
}
}
}
//NOTE: It is quite feasible to replace the implementation of this method
//with one which only loosely approximates the hypot function. I've tested
//simple approximations such as Math.abs(x) + Math.abs(y) and they work fine.
private float hypot(float x, float y) {
return (float) Math.hypot(x, y);
}
private float gaussian(float x, float sigma) {
return (float) Math.exp(-(x * x) / (2f * sigma * sigma));
}
private void performHysteresis(int low, int high) {
//NOTE: this implementation reuses the data array to store both
//luminance data from the image, and edge intensity from the processing.
//This is done for memory efficiency, other implementations may wish
//to separate these functions.
Arrays.fill(data, 0);
int offset = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (data[offset] == 0 && magnitude[offset] >= high) {
follow(x, y, offset, low);
}
offset++;
}
}
}
private void follow(int x1, int y1, int i1, int threshold) {
int x0 = x1 == 0 ? x1 : x1 - 1;
int x2 = x1 == width - 1 ? x1 : x1 + 1;
int y0 = y1 == 0 ? y1 : y1 - 1;
int y2 = y1 == height -1 ? y1 : y1 + 1;
data[i1] = magnitude[i1];
for (int x = x0; x <= x2; x++) {
for (int y = y0; y <= y2; y++) {
int i2 = x + y * width;
if ((y != y1 || x != x1)
&& data[i2] == 0
&& magnitude[i2] >= threshold) {
follow(x, y, i2, threshold);
return;
}
}
}
}
private void thresholdEdges() {
for (int i = 0; i < picsize; i++) {
data[i] = data[i] > 0 ? -1 : 0xff000000;
}
}
private int luminance(float r, float g, float b) {
return Math.round(0.299f * r + 0.587f * g + 0.114f * b);
}
private void readLuminance() {
int type = sourceImage.getType();
if (type == BufferedImage.TYPE_INT_RGB || type == BufferedImage.TYPE_INT_ARGB) {
int[] pixels = (int[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
int p = pixels[i];
int r = (p & 0xff0000) >> 16;
int g = (p & 0xff00) >> 8;
int b = p & 0xff;
data[i] = luminance(r, g, b);
}
} else if (type == BufferedImage.TYPE_BYTE_GRAY) {
byte[] pixels = (byte[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
data[i] = (pixels[i] & 0xff);
}
} else if (type == BufferedImage.TYPE_USHORT_GRAY) {
short[] pixels = (short[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
for (int i = 0; i < picsize; i++) {
data[i] = (pixels[i] & 0xffff) / 256;
}
} else if (type == BufferedImage.TYPE_3BYTE_BGR) {
byte[] pixels = (byte[]) sourceImage.getData().getDataElements(0, 0, width, height, null);
int offset = 0;
for (int i = 0; i < picsize; i++) {
int b = pixels[offset++] & 0xff;
int g = pixels[offset++] & 0xff;
int r = pixels[offset++] & 0xff;
data[i] = luminance(r, g, b);
}
} else {
throw new IllegalArgumentException("Unsupported image type: " + type);
}
}
private void normalizeContrast() {
int[] histogram = new int[256];
for (int i = 0; i < data.length; i++) {
histogram[data[i]]++;
}
int[] remap = new int[256];
int sum = 0;
int j = 0;
for (int i = 0; i < histogram.length; i++) {
sum += histogram[i];
int target = sum*255/picsize;
for (int k = j+1; k <=target; k++) {
remap[k] = i;
}
j = target;
}
for (int i = 0; i < data.length; i++) {
data[i] = remap[data[i]];
}
}
private void writeEdges(int pixels[]) {
//NOTE: There is currently no mechanism for obtaining the edge data
//in any other format other than an INT_ARGB type BufferedImage.
//This may be easily remedied by providing alternative accessors.
if (edgesImage == null) {
edgesImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
edgesImage.getWritableTile(0, 0).setDataElements(0, 0, width, height, pixels);
}
}
public static void main(String []Args){
//create the detector
CannyEdgeDetector detector = new CannyEdgeDetector();
//adjust its parameters as desired
detector.setLowThreshold(0.5f);
detector.setHighThreshold(1f);
//apply it to an image
detector.setSourceImage(frame);
detector.process();
BufferedImage edges = detector.getEdgesImage();
}
}
Why don't you just read a test image from a file? That way, you can verify that it's working properly before transferring.
I'm trying to convert the following code (from Wikipedia) from Java to JavaScript:
/*
* 3 June 2003, [[:en:User:Cyp]]:
* Maze, generated by my algorithm
* 24 October 2006, [[:en:User:quin]]:
* Source edited for clarity
* 25 January 2009, [[:en:User:DebateG]]:
* Source edited again for clarity and reusability
* 1 June 2009, [[:en:User:Nandhp]]:
* Source edited to produce SVG file when run from the command-line
*
* This program was originally written by [[:en:User:Cyp]], who
* attached it to the image description page for an image generated by
* it on en.wikipedia. The image was licensed under CC-BY-SA-3.0/GFDL.
*/
import java.awt.*;
import java.applet.*;
import java.util.Random;
/* Define the bit masks */
class Constants {
public static final int WALL_ABOVE = 1;
public static final int WALL_BELOW = 2;
public static final int WALL_LEFT = 4;
public static final int WALL_RIGHT = 8;
public static final int QUEUED = 16;
public static final int IN_MAZE = 32;
}
public class Maze extends java.applet.Applet {
/* The width and height (in cells) of the maze */
private int width;
private int height;
private int maze[][];
private static final Random rnd = new Random();
/* The width in pixels of each cell */
private int cell_width;
/* Construct a Maze with the default width, height, and cell_width */
public Maze() {
this(20,20,10);
}
/* Construct a Maze with specified width, height, and cell_width */
public Maze(int width, int height, int cell_width) {
this.width = width;
this.height = height;
this.cell_width = cell_width;
}
/* Initialization method that will be called when the program is
* run from the command-line. Maze will be written as SVG file. */
public static void main(String[] args) {
Maze m = new Maze();
m.createMaze();
m.printSVG();
}
/* Initialization method that will be called when the program is
* run as an applet. Maze will be displayed on-screen. */
public void init() {
createMaze();
}
/* The maze generation algorithm. */
private void createMaze(){
int x, y, n, d;
int dx[] = { 0, 0, -1, 1 };
int dy[] = { -1, 1, 0, 0 };
int todo[] = new int[height * width], todonum = 0;
/* We want to create a maze on a grid. */
maze = new int[width][height];
/* We start with a grid full of walls. */
for (x = 0; x < width; ++x) {
for (y = 0; y < height; ++y) {
if (x == 0 || x == width - 1 || y == 0 || y == height - 1) {
maze[x][y] = Constants.IN_MAZE;
} else {
maze[x][y] = 63;
}
}
}
/* Select any square of the grid, to start with. */
x = 1 + rnd.nextInt (width - 2);
y = 1 + rnd.nextInt (height - 2);
/* Mark this square as connected to the maze. */
maze[x][y] &= ~48;
/* Remember the surrounding squares, as we will */
for (d = 0; d < 4; ++d) {
if ((maze[][d][][d] & Constants.QUEUED) != 0) {
/* want to connect them to the maze. */
todo[todonum++] = ((x + dx[d]) << Constants.QUEUED) | (y + dy[d]);
maze[][d][][d] &= ~Constants.QUEUED;
}
}
/* We won't be finished until all is connected. */
while (todonum > 0) {
/* We select one of the squares next to the maze. */
n = rnd.nextInt (todonum);
x = todo[n] >> 16; /* the top 2 bytes of the data */
y = todo[n] & 65535; /* the bottom 2 bytes of the data */
/* We will connect it, so remove it from the queue. */
todo[n] = todo[--todonum];
/* Select a direction, which leads to the maze. */
do {
d = rnd.nextInt (4);
}
while ((maze[][d][][d] & Constants.IN_MAZE) != 0);
/* Connect this square to the maze. */
maze[x][y] &= ~((1 << d) | Constants.IN_MAZE);
maze[][d][][d] &= ~(1 << (d ^ 1));
/* Remember the surrounding squares, which aren't */
for (d = 0; d < 4; ++d) {
if ((maze[][d][][d] & Constants.QUEUED) != 0) {
/* connected to the maze, and aren't yet queued to be. */
todo[todonum++] = ((x + dx[d]) << Constants.QUEUED) | (y + dy[d]);
maze[][d][][d] &= ~Constants.QUEUED;
}
}
/* Repeat until finished. */
}
/* Add an entrance and exit. */
maze[1][1] &= ~Constants.WALL_ABOVE;
maze[width - 2][height - 2] &= ~Constants.WALL_BELOW;
}
/* Called by the applet infrastructure to display the maze on-screen. */
public void paint(Graphics g) {
drawMaze(g);
}
/* Called to write the maze to an SVG file. */
public void printSVG() {
System.out.format("<svg width=\"%d\" height=\"%d\" version=\"1.1\""
+ " xmlns=\"http://www.w3.org/2000/svg\">\n",
width*cell_width, height*cell_width);
System.out.println(" <g stroke=\"black\" stroke-width=\"1\""
+ " stroke-linecap=\"round\">");
drawMaze(null);
System.out.println(" </g>\n</svg>");
}
/* Main maze-drawing loop. */
public void drawMaze(Graphics g) {
int x, y;
for (x = 1; x < width - 1; ++x) {
for (y = 1; y < height - 1; ++y) {
if ((maze[x][y] & Constants.WALL_ABOVE) != 0)
drawLine( x * cell_width, y * cell_width,
(x + 1) * cell_width, y * cell_width, g);
if ((maze[x][y] & Constants.WALL_BELOW) != 0)
drawLine( x * cell_width, (y + 1) * cell_width,
(x + 1) * cell_width, (y + 1) * cell_width, g);
if ((maze[x][y] & Constants.WALL_LEFT) != 0)
drawLine( x * cell_width, y * cell_width,
x * cell_width, (y + 1) * cell_width, g);
if ((maze[x][y] & Constants.WALL_RIGHT) != 0)
drawLine((x + 1) * cell_width, y * cell_width,
(x + 1) * cell_width, (y + 1) * cell_width, g);
}
}
}
/* Draw a line, either in the SVG file or on the screen. */
public void drawLine(int x1, int y1, int x2, int y2, Graphics g) {
if ( g != null ) g.drawLine(x1, y1, x2, y2);
else System.out.format(" <line x1=\"%d\" y1=\"%d\""
+ " x2=\"%d\" y2=\"%d\" />\n", x1, y1, x2, y2);
}
}
Anyway, I was chugging along fairly quickly when I came to a bit that I just don't understand:
/* Remember the surrounding squares, as we will */
for (var d = 0; d < 4; ++d) {
if ((maze[][d][][d] & Constants.QUEUED) != 0) {
/* want to connect them to the maze. */
todo[todonum++] = ((x + dx[d]) << Constants.QUEUED) | (y + dy[d]);
maze[][d][][d] &= ~Constants.QUEUED;
}
}
What I don't get is why there are four sets of brackets following the "maze" parameter instead of just two, since "maze" is a two dimensional array, not a four dimensional array.
I'm sure there's a good reason for this. Problem is, I just don't get it.
Thanks!
To simply correct the syntax, this answer is as good as any. However to fix the algorithm, Leon's has more insight there.
Looks to me like the code is wrong having two too many sets of square brackets. maze is declared and initialized as a 2-dimensional int array. There's no reason it should have more than that. Perhaps the result of a misinformed or buggy code generator?
Just remove the preceding, empty square brackets [] (or ][) from the array accesses and you'll be set.
My educated guess would be that there is some code missing. Removing the extra [] makes the code compile but it does not generate a maze but is stuck in an infinite loop
I think [][d][][d] are supposed to be maze[x+dx[d]][y+dy[d]].
It is clear that d is supposed to index into dx and dy arrays, which are offsets into the neighbours of the current cell, as is is always iterated 4 times. Also todo[todonum++] = ((x + dx[d]) << Constants.QUEUED) | (y + dy[d]); indexes the maze array using dx[d] and dy[y] as offsets to the neighbours.
I am not sure whether this is the best way thou, as when x=0 and dx offset is -1 you will have and OutOfBoundsException. You may have to handle those cases explicitly.
OK, here's the JavaScript version:
/*
* 3 June 2003, [[:en:User:Cyp]]:
* Maze, generated by my algorithm
* 24 October 2006, [[:en:User:quin]]:
* Source edited for clarity
* 25 January 2009, [[:en:User:DebateG]]:
* Source edited again for clarity and reusability
* 1 June 2009, [[:en:User:Nandhp]]:
* Source edited to produce SVG file when run from the command-line
* 7 January, 2011 [[:en:User:SharkD]]:
* Source converted to JavaScript
*
* This program was originally written by [[:en:User:Cyp]], who
* attached it to the image description page for an image generated by
* it on en.wikipedia. The image was licensed under CC-BY-SA-3.0/GFDL.
*/
/* Recreate a math function that exists in Java but not JavaScript. */
Math.nextInt = function (number) {
return Math.floor(Math.random() * number)
}
/* Recreate a system function that exists in Java but not JavaScript.
* Uncomment either WScript.Echo() or alert() depending on whether you are
* running the script from the Windows command-line or a Web page.
*/
function println(string)
{
// if inside Windows Scripting Host
// WScript.Echo(string)
// if inside a Web page
alert(string)
}
/* Define the bit masks */
var Constants =
{
WALL_ABOVE : 1,
WALL_BELOW : 2,
WALL_LEFT : 4,
WALL_RIGHT : 8,
QUEUED : 16,
IN_MAZE : 32
}
/* Construct a Maze with specified width, height, and cell_width */
function Maze(width, height, cell_width) {
if (width)
this.width = width;
else
this.width = 20;
if (height)
this.height = height;
else
this.height = 20;
if (cell_width)
this.cell_width = cell_width;
else
this.cell_width = 10;
this.maze = []
/* The maze generation algorithm. */
this.createMaze = function() {
var width = this.width
var height = this.height
var maze = this.maze
var x, y, n, d;
var dx = [ 0, 0, -1, 1 ];
var dy = [ -1, 1, 0, 0 ];
var todo = new Array(height * width);
var todonum = 0;
/* We want to create a maze on a grid. */
/* We start with a grid full of walls. */
for (x = 0; x < width; ++x) {
maze[x] = []
for (y = 0; y < height; ++y) {
if (x == 0 || x == width - 1 || y == 0 || y == height - 1) {
maze[x][y] = Constants.IN_MAZE;
}
else {
maze[x][y] = 63;
}
}
}
/* Select any square of the grid, to start with. */
x = 1 + Math.nextInt(width - 2);
y = 1 + Math.nextInt(height - 2);
/* Mark this square as connected to the maze. */
maze[x][y] &= ~48;
/* Remember the surrounding squares, as we will */
for (d = 0; d < 4; ++d) {
if ((maze[x + dx[d]][y + dy[d]] & Constants.QUEUED) != 0) {
/* want to connect them to the maze. */
todo[todonum++] = ((x + dx[d]) << Constants.QUEUED) | (y + dy[d]);
maze[x + dx[d]][y + dy[d]] &= ~Constants.QUEUED;
}
}
/* We won't be finished until all is connected. */
while (todonum > 0) {
/* We select one of the squares next to the maze. */
n = Math.nextInt(todonum);
x = todo[n] >> 16; /* the top 2 bytes of the data */
y = todo[n] & 65535; /* the bottom 2 bytes of the data */
/* We will connect it, so remove it from the queue. */
todo[n] = todo[--todonum];
/* Select a direction, which leads to the maze. */
do {
d = Math.nextInt(4);
}
while ((maze[x + dx[d]][y + dy[d]] & Constants.IN_MAZE) != 0);
/* Connect this square to the maze. */
maze[x][y] &= ~((1 << d) | Constants.IN_MAZE);
maze[x + dx[d]][y + dy[d]] &= ~(1 << (d ^ 1));
/* Remember the surrounding squares, which aren't */
for (d = 0; d < 4; ++d) {
if ((maze[x + dx[d]][y + dy[d]] & Constants.QUEUED) != 0) {
/* connected to the maze, and aren't yet queued to be. */
todo[todonum++] = ((x + dx[d]) << Constants.QUEUED) | (y + dy[d]);
maze[x + dx[d]][y + dy[d]] &= ~Constants.QUEUED;
}
}
/* Repeat until finished. */
}
/* Add an entrance and exit. */
maze[1][1] &= ~Constants.WALL_ABOVE;
maze[width - 2][height - 2] &= ~Constants.WALL_BELOW;
}
/* Called to write the maze to an SVG file. */
this.printSVG = function () {
var outstring = "<svg width=\"" + (width * cell_width) + "\" height=\"" + (height*cell_width) + "\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n"
+ " <g stroke=\"black\" stroke-width=\"1\" stroke-linecap=\"round\">\n"
+ this.drawMaze()
+ " </g>\n</svg>\n";
println(outstring)
}
/* Main maze-drawing loop. */
this.drawMaze = function () {
var x, y;
var width = this.width;
var height = this.height;
var cell_width = this.cell_width
var outstring = ""
for (x = 1; x < width - 1; ++x) {
for (y = 1; y < height - 1; ++y) {
if ((this.maze[x][y] & Constants.WALL_ABOVE) != 0)
outstring += this.drawLine( x * cell_width, y * cell_width, (x + 1) * cell_width, y * cell_width);
if ((this.maze[x][y] & Constants.WALL_BELOW) != 0)
outstring += this.drawLine( x * cell_width, (y + 1) * cell_width, (x + 1) * cell_width, (y + 1) * cell_width);
if ((this.maze[x][y] & Constants.WALL_LEFT) != 0)
outstring += this.drawLine( x * cell_width, y * cell_width, x * cell_width, (y + 1) * cell_width);
if ((this.maze[x][y] & Constants.WALL_RIGHT) != 0)
outstring += this.drawLine((x + 1) * cell_width, y * cell_width, (x + 1) * cell_width, (y + 1) * cell_width);
}
}
return outstring
}
/* Draw a line, either in the SVG file or on the screen. */
this.drawLine = function (x1, y1, x2, y2) {
return " <line x1=\"" + x1 + "\" y1=\"" + y1 + "\" x2=\"" + x2 + "\" y2=\"" + y2 + "\" />\n";
}
}
/* Initialization method that will be called when the program is
* run from the command-line. Maze will be written as SVG file. */
function main(args) {
var m = new Maze();
m.createMaze();
m.printSVG();
}
/* execute the program */
main()
Next step is to turn it into a six axis 3D maze and convert to Lua, which is my actual target platform. :)
PS - I'm trying to vote on answers, but it's telling me I need to login or register, wtf?