Image filter - Convolution - java

I am trying to implement image filter ( using convulution). I've spent all day trying to figure out what's going on and I cannot find a mistake. The filter works only when I use it to blur the image. In other cases it doesn't work properly: For example this is the original picture(before filtering):
And this is the picture after filtering with this matrix:
I use Marvin Image Processing Framework and jblas library in my code:
public class FiltrySploty extends MarvinAbstractImagePlugin {
#Override
public void load() {
}
#Override
public MarvinAttributesPanel getAttributesPanel() {
return null;
}
#Override
public void process(
MarvinImage imageIn,
MarvinImage imageOut,
MarvinAttributes attributesOut,
MarvinImageMask mask,
boolean previewMode) {
double norm=0;
DoubleMatrix filter = (DoubleMatrix)getAttribute("filter");
for ( int i = 0; i < filter.getRows();i++)
{
for ( int j = 0; j < filter.getColumns();j++)
{
norm=norm+filter.get(i, j);
}
}
int marginx = ((filter.getRows()-1)/2);
int marginy = ((filter.getColumns()-1)/2);
for (int x = marginx; x < imageIn.getWidth()-marginx; x++) {
for (int y = marginy; y < imageIn.getHeight()-marginy; y++) {
double SumRed=0;
double SumGreen=0;
double SumBlue=0;
for ( int i = x-marginx,k=0 ; k < filter.getRows();i++,k++)
{
for ( int j = y-marginy, l=0 ; l <filter.getColumns();j++,l++)
{
SumRed= SumRed+(filter.get(k, l)*imageIn.getIntComponent0(i, j));
SumGreen= SumGreen+(filter.get(k, l)*imageIn.getIntComponent1(i, j));
SumBlue= SumBlue+(filter.get(k, l)*imageIn.getIntComponent2(i, j));
}
}
SumRed = SumRed/norm;
SumGreen = SumGreen/norm;
SumBlue = SumBlue/norm; // normalization
if(SumRed>255.0) SumRed=255.0;
else if(SumRed<0.0) SumRed=0.0;
if(SumGreen>255.0) SumGreen=255.0;
else if(SumGreen<0.0) SumGreen=0.0;
if(SumBlue>255.0) SumBlue=255.0;
else if(SumBlue<0.0) SumBlue=0.0;
imageOut.setIntColor(x, y, (int)(SumRed), (int)(SumGreen), (int)(SumBlue));
}
}
}
}
Seeing the effect of filtering I suppose that the SumRed, SumGreen and SumBlue are out of range and they are setting to 255 or 0 values. But I have no idea why.

Related

Checking to see if two 2D boolean arrays are equal at a given interval: Java

I have two 2d boolean arrays, the smaller array (shape) is going over the larger array (world).
I am having trouble to find a method to find out when the smaller array can "fit" into the larger one.
When I run the code it either just goes through the larger array, never stopping, or stops after one step (incorrectly).
public void solve() {
ArrayList<Boolean> worldList=new ArrayList<>();
ArrayList<Boolean> shapeList=new ArrayList<>();
for (int i = 0; i < world.length; i++) {
for (int k = 0; k < world[i].length; k++) {
worldList.add(world[i][k]);
display(i, k, Orientation.ROTATE_NONE);
for (int j = 0; j < shape.length; j++) {
for (int l = 0; l < shape[j].length; l++) {
shapeList.add(shape[j][l]);
if(shapeList.equals(worldList)) {
return;
}
}
}
}
}
}
A good place to start with a problem like this is brute force for the simplest case. So, for each index in the world list, just check to see if every following index of world and shapes match.
Notice we only iterate to world.size()-shapes.size(), because naturally if shapes is longer than the portion of world we haven't checked, it won't fit.
import java.util.ArrayList;
public class Test {
ArrayList<Boolean> world = new ArrayList<>();
ArrayList<Boolean> shapes = new ArrayList<>();
public static void main(String[] args) {
new Work();
}
public Test() {
world.add(true);
world.add(false);
world.add(false);
world.add(true);
shapes.add(false);
shapes.add(true);
// Arraylists initialized to these values:
// world: T F F T
// shapes: F T
System.out.println(getFitIndex());
}
/**
* Get the index of the fit, -1 if it won't fit.
* #return
*/
public int getFitIndex() {
for (int w = 0; w <= world.size()-shapes.size(); w++) {
boolean fits = true;
for (int s = 0; s < shapes.size(); s++) {
System.out.println("Compare shapes[" + s + "] and world["+ (w+s) + "]: " +
shapes.get(s).equals(world.get(w+s)));
if (!shapes.get(s).equals(world.get(w+s))) fits = false;
}
System.out.println();
if (fits) return w;
}
return -1;
}
}
When we run this code, we get a value of 2 printed to the console, since shapes does indeed fit inside world, starting at world[2].
You can find the row and column of fitting like this
public void fit() {
int h = world.length - shape.length;
int w = world[0].length - shape[0].length;
for (int i = 0; i <= h; i++) {
for (int k = 0; k <= w; k++) {
boolean found = true;
for (int j = 0; j < shape.length && found; j++) {
for (int l = 0; l < shape[j].length && found; l++) {
if (shape[j][l] != world[i + j][k + l])
found = false;
}
}
if (found) {
//Your shape list fit the world list at starting index (i, k)
//You can for example save the i, k variable in instance variable
//Or return then as an object for further use
return;
}
}
}

What am I doing wrong with my image denoising method?

I've been trying to denoise my image by using a median filter as described in this article
I'm only doing one pass until I get this thing working. The result is largely a washed out image, as seen below.
A minimal working version of my code is below:
import java.awt.image.BufferedImage;
import java.util.Arrays;
public class Denoise {
public static void main(String args[]) {
String directory = "C:\\Users\\Speedy Octopus\\Desktop\\Place Car Folders Here\\Original\\15.JPG";
BufferedImage image = ImageUtility.loadImage(directory);
for (int iterationCount = 0; iterationCount < 1; iterationCount++){
for (int i = 1; i < image.getWidth()-1; i++) {
for (int j = 1; j < image.getHeight()-1; j++) {
image.setRGB(i, j, getMedianPixelValue(image, i, j));
}
}
}
String directory2 = "C:\\Users\\Speedy Octopus\\Desktop\\Place Car Folders Here\\Original\\152.JPG";
Controller.saveImage(image, directory2);
}
public static int getMedianPixelValue(BufferedImage image, int i, int j) {
int[] surroundingPixels = new int[8];
int iter = 0;
for (int q = i-1; q<=i+1; q++) {
for (int r = j-1; r<=j+1;r++) {
if (!(q == i && r == j)) {
surroundingPixels[iter] = image.getRGB(q, r);
iter++;
}
}
}
Arrays.sort(surroundingPixels);
int medianIndex = surroundingPixels.length/2;
int medianPixel = surroundingPixels[medianIndex];
return medianPixel;
}
}
As I answered in this question Applying Mean filter on an image using java getRGB "Returns an integer pixel in the default RGB color model (TYPE_INT_ARGB)" so you have to extract and remove the alpha (A) component before you do any comparisons:
pixel=image.getRGB(i, j)&0x00ffffff;
in the media sorting etc
And you can extract the R, G, and B and process them separately, or do the comparison on the whole pixel RGB - you can experiment either way.

How to print a matrix in clockwise order

There is matrix for [x][y] order. i want to print its value in clockwise order
I have tried several methods but unable to write the logic of the code. I'm trying it in java but logic is important so you can help me in any language.
When I read your post I've started to play so I'll post you my code maybe it will be halpful for you. I've did it for square if you want for rectangle one need separate stepX and stepY. SIZE would be input parameter in your case, I have it final static for test.
public class clockwise {
private static final int SIZE = 3;
public static void main(String[] args) {
// int[][] test_matrix = {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
int[][] test_matrix = {{1,2,3},{5,6,7},{9,10,11}};
int[][] direction = {{1, 0},{0, 1},{-1, 0},{0, -1}}; //{x,y}
for(int i = 0; i < SIZE; i++) {
for(int j = 0; j < SIZE; j++)
System.out.print(test_matrix[i][j] + " ");
System.out.println("");
}
int x = 0;
int y = 0;
int directionMove = 0;
int stepSize = SIZE;
boolean changeStep = true;
int stepCounter = 0;
for(int i = 0; i < SIZE*SIZE; i++) {
System.out.print(test_matrix[x][y] + " ");
stepCounter++;
if (stepCounter % stepSize == 0) {
directionMove++;
directionMove = directionMove%4;
if(changeStep) { //after first edge one need to decrees step after passing two edges
stepSize--;
changeStep = false;
} else {
changeStep = true;
}
stepCounter = 0;
}
x += direction[directionMove][0];
y += direction[directionMove][1];
}
}
}

Processing P3D sketch not working in Javascript mode

I Am trying to convert a project I've built in Processing [Java Mode] to be able to be viewed on the web.The project utilizes P3D rendering to display a 3D world that can be rotated and also can change 3 data sets (.tsv files) that map the location and size of earthquakes that occurred during three years. The following code works fine when ran in Java but when I run in Javascript mode I only get a blank gray screen. The HTML to embed the sketch into the canvas is correct and there are no errors so I am sure it is something within the code that that is not compatible with Javascript. Is there a way to just run the PDE as java without having to use Processing.js otherwise is there anything that is noticeably wrong with the code in regards to compatibility with processing.js?
Here is a link to the sketch that is not working if that helps .
Any suggestions as to what I should do is greatly appreciated. Thanks
float dial = 0.0;
FloatTable data;
FloatTable data2;
float dataMin, dataMax;
int rowCount;
int currentColumn = 0;
int columnCount;
int yearMin, yearMax;
int[] years;
int yearInterval = 10;
int volumeInterval = 10;
int volumeIntervalMinor=5;
PImage bg;
PImage texmap;
PFont font;
PImage img, img2;
PImage imgTitle;
int sDetail = 35; // Sphere detail setting
float rotationX = 0;
float rotationY = 0;
float velocityX = 0;
float velocityY = 0;
float globeRadius = 750;
float pushBack = 0;
float[] cx, cz, sphereX, sphereY, sphereZ;
float sinLUT[];
float cosLUT[];
float SINCOS_PRECISION = 0.5;
int SINCOS_LENGTH = int(360.0 / SINCOS_PRECISION);
void setup() {
size(1300,1000, P3D);
textSize(100);
texmap = loadImage("img/world32k.jpg");
initializeSphere(sDetail);
setupstuff("2011.tsv");
}
void draw() {
background(0);
// drawTitle();
drawMain();
renderGlobe();
drawDial();
}
void setupstuff(String filename){
data = new FloatTable(filename);
rowCount=data.getRowCount();
columnCount = data.getColumnCount();
years = int(data.getRowNames());
yearMin = years[0];
yearMax = years[years.length - 1];
font = loadFont("Univers-Bold-12.vlw");
dataMin = 0;
dataMax = ceil( data.getTableMax()/volumeInterval)*volumeInterval;
img = loadImage("img/dialface.jpg");
img2 = loadImage("img/dial.png");
imgTitle = loadImage("Title.png");
imageMode(CENTER);
}
void renderGlobe() {
pushMatrix();
translate(width * 0.33, height * 0.4, pushBack);
// pushMatrix();
noFill();
stroke(255,200);
strokeWeight(2);
smooth();
//popMatrix();
pointLight(201, 252, 276, width * 0.3, height * 0.2, 660);
pushMatrix();
rotateX( radians(-rotationX) );
rotateY( radians(180 - rotationY) );
fill(200);
noStroke();
textureMode(IMAGE);
texturedSphere(globeRadius, texmap);
popMatrix();
drawDots(0);
popMatrix();
rotationX += velocityX;
rotationY += velocityY;
velocityX *= 0.95;
velocityY *= 0.95;
// Implements mouse control (interaction will be inverse when sphere is upside down)
if(mousePressed){
velocityX += (mouseY-pmouseY) * 0.01;
velocityY -= (mouseX-pmouseX) * 0.01;
}
}
void initializeSphere(int res)
{
sinLUT = new float[SINCOS_LENGTH];
cosLUT = new float[SINCOS_LENGTH];
for (int i = 0; i < SINCOS_LENGTH; i++) {
sinLUT[i] = (float) Math.sin(i * DEG_TO_RAD * SINCOS_PRECISION);
cosLUT[i] = (float) Math.cos(i * DEG_TO_RAD * SINCOS_PRECISION);
}
float delta = (float)SINCOS_LENGTH/res;
float[] cx = new float[res];
float[] cz = new float[res];
// Calc unit circle in XZ plane
for (int i = 0; i < res; i++) {
cx[i] = -cosLUT[(int) (i*delta) % SINCOS_LENGTH];
cz[i] = sinLUT[(int) (i*delta) % SINCOS_LENGTH];
}
// Computing vertexlist vertexlist starts at south pole
int vertCount = res * (res-1) + 2;
int currVert = 0;
// Re-init arrays to store vertices
sphereX = new float[vertCount];
sphereY = new float[vertCount];
sphereZ = new float[vertCount];
float angle_step = (SINCOS_LENGTH*0.5f)/res;
float angle = angle_step;
// Step along Y axis
for (int i = 1; i < res; i++) {
float curradius = sinLUT[(int) angle % SINCOS_LENGTH];
float currY = -cosLUT[(int) angle % SINCOS_LENGTH];
for (int j = 0; j < res; j++) {
sphereX[currVert] = cx[j] * curradius;
sphereY[currVert] = currY;
sphereZ[currVert++] = cz[j] * curradius;
}
angle += angle_step;
}
sDetail = res;
}
// Draw texture sphere
void texturedSphere(float r, PImage t) {
int v1,v11,v2;
r = (r + 240 ) * 0.33;
beginShape(TRIANGLE_STRIP);
texture(t);
float iu=(float)(t.width-1)/(sDetail);
float iv=(float)(t.height-1)/(sDetail);
float u=0,v=iv;
for (int i = 0; i < sDetail; i++) {
vertex(0, -r, 0,u,0);
vertex(sphereX[i]*r, sphereY[i]*r, sphereZ[i]*r, u, v);
u+=iu;
}
vertex(0, -r, 0,u,0);
vertex(sphereX[0]*r, sphereY[0]*r, sphereZ[0]*r, u, v);
endShape();
// Middle rings
int voff = 0;
for(int i = 2; i < sDetail; i++) {
v1=v11=voff;
voff += sDetail;
v2=voff;
u=0;
beginShape(TRIANGLE_STRIP);
texture(t);
for (int j = 0; j < sDetail; j++) {
vertex(sphereX[v1]*r, sphereY[v1]*r, sphereZ[v1++]*r, u, v);
vertex(sphereX[v2]*r, sphereY[v2]*r, sphereZ[v2++]*r, u, v+iv);
u+=iu;
}
// Close each ring
v1=v11;
v2=voff;
vertex(sphereX[v1]*r, sphereY[v1]*r, sphereZ[v1]*r, u, v);
vertex(sphereX[v2]*r, sphereY[v2]*r, sphereZ[v2]*r, u, v+iv);
endShape();
v+=iv;
}
u=0;
// Add the northern cap
beginShape(TRIANGLE_STRIP);
texture(t);
for (int i = 0; i < sDetail; i++) {
v2 = voff + i;
vertex(sphereX[v2]*r, sphereY[v2]*r, sphereZ[v2]*r, u, v);
vertex(0, r, 0,u,v+iv);
u+=iu;
}
vertex(sphereX[voff]*r, sphereY[voff]*r, sphereZ[voff]*r, u, v);
endShape();
}
//draw dot
void drawDots(float r){
r = (r + 240 ) * 0.33;
blendMode(ADD);
stroke(255, 100, 0, 100);
rotateX( radians(-rotationX) );
rotateY( radians(270 - rotationY) );
for(int row = 0; row<rowCount; row++){
int col2 = 0;
int col3 = 1;
int col4 = 2;
float latY = data.getFloat(row, col2);
float longX = data.getFloat(row, col3);
float ricter = data.getFloat(row, col4);
pushMatrix();
rotateX(radians(-latY));
rotateY(radians(longX));
translate(0,0,r+250);
ellipse(0,0,ricter*ricter*ricter/20,ricter*ricter*ricter/20);
popMatrix();
println(latY);
}
rotationX += velocityX;
rotationY += velocityY;
velocityX *= 0.95;
velocityY *= 0.95;
}
void drawDial(){
translate(width-250, height-250);
image(img, -9/2, 53/2, 830/2,626/2 );
if(keyPressed){
if (key == '1') {
dial = radians( 0 );
setupstuff("2011.tsv");
}
if (key == '2') {
dial = radians( 120 );
setupstuff("2012.tsv");
}
if (key == '3') {
dial = radians( 240 );
setupstuff("2010.tsv");
}
}
rotate(dial);
image(img2, 103/2, -65/2, 314/2, 400/2);
}
void drawMain(){
image(imgTitle, width-320, 170);
}
void drawTitle(){
pushMatrix();
fill(255,255, 255);
translate(width-300, 120);
textFont(font, 32);
text("Earthquakes", 10, 50);
popMatrix();
}
class FloatTable {
int rowCount;
int columnCount;
float[][] data;
String[] rowNames;
String[] columnNames;
FloatTable(String filename) {
String[] rows = loadStrings(filename);
String[] columns = split(rows[0], TAB);
columnNames = subset(columns, 1); // upper-left corner ignored
scrubQuotes(columnNames);
columnCount = columnNames.length;
rowNames = new String[rows.length-1];
data = new float[rows.length-1][];
// start reading at row 1, because the first row was only the column headers
for (int i = 1; i < rows.length; i++) {
if (trim(rows[i]).length() == 0) {
continue; // skip empty rows
}
if (rows[i].startsWith("#")) {
continue; // skip comment lines
}
// split the row on the tabs
String[] pieces = split(rows[i], TAB);
scrubQuotes(pieces);
// copy row title
rowNames[rowCount] = pieces[0];
// copy data into the table starting at pieces[1]
data[rowCount] = parseFloat(subset(pieces, 1));
// increment the number of valid rows found so far
rowCount++;
}
// resize the 'data' array as necessary
data = (float[][]) subset(data, 0, rowCount);
}
void scrubQuotes(String[] array) {
for (int i = 0; i < array.length; i++) {
if (array[i].length() > 2) {
// remove quotes at start and end, if present
if (array[i].startsWith("\"") && array[i].endsWith("\"")) {
array[i] = array[i].substring(1, array[i].length() - 1);
}
}
// make double quotes into single quotes
array[i] = array[i].replaceAll("\"\"", "\"");
}
}
int getRowCount() {
return rowCount;
}
String getRowName(int rowIndex) {
return rowNames[rowIndex];
}
String[] getRowNames() {
return rowNames;
}
int getRowIndex(String name) {
for (int i = 0; i < rowCount; i++) {
if (rowNames[i].equals(name)) {
return i;
}
}
//println("No row named '" + name + "' was found");
return -1;
}
int getColumnCount() {
return columnCount;
}
String getColumnName(int colIndex) {
return columnNames[colIndex];
}
String[] getColumnNames() {
return columnNames;
}
float getFloat(int rowIndex, int col) {
if ((rowIndex < 0) || (rowIndex >= data.length)) {
throw new RuntimeException("There is no row " + rowIndex);
}
if ((col < 0) || (col >= data[rowIndex].length)) {
throw new RuntimeException("Row " + rowIndex + " does not have a column " + col);
}
return data[rowIndex][col];
}
boolean isValid(int row, int col) {
if (row < 0) return false;
if (row >= rowCount) return false;
//if (col >= columnCount) return false;
if (col >= data[row].length) return false;
if (col < 0) return false;
return !Float.isNaN(data[row][col]);
}
float getColumnMin(int col) {
float m = Float.MAX_VALUE;
for (int row = 0; row < rowCount; row++) {
if (isValid(row, col)) {
if (data[row][col] < m) {
m = data[row][col];
}
}
}
return m;
}
float getColumnMax(int col) {
float m = -Float.MAX_VALUE;
for (int row = 0; row < rowCount; row++) {
if (isValid(row, col)) {
if (data[row][col] > m) {
m = data[row][col];
}
}
}
return m;
}
float getRowMin(int row) {
float m = Float.MAX_VALUE;
for (int col = 0; col < columnCount; col++) {
if (isValid(row, col)) {
if (data[row][col] < m) {
m = data[row][col];
}
}
}
return m;
}
float getRowMax(int row) {
float m = -Float.MAX_VALUE;
for (int col = 0; col < columnCount; col++) {
if (isValid(row, col)) {
if (data[row][col] > m) {
m = data[row][col];
}
}
}
return m;
}
float getTableMin() {
float m = Float.MAX_VALUE;
for (int row = 0; row < rowCount; row++) {
for (int col = 0; col < columnCount; col++) {
if (isValid(row, col)) {
if (data[row][col] < m) {
m = data[row][col];
}
}
}
}
return m;
}
float getTableMax() {
float m = -Float.MAX_VALUE;
for (int row = 0; row < rowCount; row++) {
for (int col = 0; col < columnCount; col++) {
if (isValid(row, col)) {
if (data[row][col] > m) {
m = data[row][col];
}
}
}
}
return m;
}
}
UPDATE
Your error about Float might be related to this line:
return !Float.isNaN(data[row][col]);
Javascript is not going to recognize Float, that is a Java construct and not fully implemented within Processing. You need to find something else to make that comparison such as: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN
Similarly all the statements like the following
float m = Float.MAX_VALUE;
are not going to work with ProcessingJS. You might want to look at: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity
ProcessingJS is extremely difficult to debug so you need to make sure you avoid everything that is only Java and not fully implemented in Processing. You need to also avoid variable name clash. Read the following to get an idea about what else could go wrong: http://processingjs.org/articles/p5QuickStart.html#thingstoknowusingpjs
Also the following method does not have a closing brace:
float getTableMin() {
float m = Float.MAX_VALUE;
for (int row = 0; row < rowCount; row++) {
for (int col = 0; col < columnCount; col++) {
if (isValid(row, col)) {
if (data[row][col] < m) {
m = data[row][col];
}
}
}
}
return m;
Fix that by adding a closing brace like so:
float getTableMin() {
float m = Float.MAX_VALUE;
for (int row = 0; row < rowCount; row++) {
for (int col = 0; col < columnCount; col++) {
if (isValid(row, col)) {
if (data[row][col] < m) {
m = data[row][col];
}
}
}
}
return m;
}
This is why it helps to have properly formatted code. In your Processing IDE, select your code and then hit Ctrl-t, that should auto-format your code to something more easily readable.
By the way, your code wouldn't even run in Java mode without this fix. It will throw an unexpected token: float exception for the method in question.
UPDATE 2
To be able to get rid of your Float related code, you can change the following line
return !Float.isNaN(data[row][col]);
to
return isNaN(data[row][col]);
And you can change all instances of Float.MAX_VALUE; to Number.MAX_VALUE;.
After changing all the instances to Javascript friendly code, I too get the error you're getting. I will try to see if I can figure it out and update the answer.
Instead of using fullscreen() use this code:
size(window.innerWidth, window.innerHeight, P3D);
NOTE: Does not work in IE.
If IE:
void setup() {
if (window.innerWidth) {
wW = window.innerWidth;
wH = window.innerHeight;
} else {
var cW = document.body.offsetWidth;
var cH = document.body.offsetHeight;
window.resizeTo(500,500);
var barsW = 500 - document.body.offsetWidth;
var barsH = 500 - document.body.offsetHeight;
wW = barsW + cW;
wH = barsH + cH;
window.resizeTo(wW,wH);
}
size(wW,wH,P3D);
}

Change order of for loops?

I have a situation where i need to loop though xyz coordinates in different orders depending on a users input. So i an area in 3D space then a set of for loops like so.
for(int x = 0; x < build.getWidth(); x++){
for(int y = 0; y < build.getHeight(); y++){
for(int z = 0; z < build.getLength(); z++){
//do stuff
}
}
}
but depending on the users input, the order may be like this.
for(int z = 0; z < build.getLenght(); z++){
for(int y = 0; y < build.getHeight(); y++){
for(int x = 0; x < build.getWidth(); x++){
//do stuff
}
}
}
or even negative.
for(int x = build.getWidth(); x > 0; x--){
for(int y = 0; y < build.getHeight(); y++){
for(int z = 0; z < build.getLength(); z++){
//do stuff
}
}
}
Is there any way to do this without hard coding every case?
Here's an n-dimensional stepper that can step in any number of dimensions in any order from any start locations to any limits. See the test code for an example.
public class Test {
public void test() {
int[] limits = {3, -5, 7};
int[] order = {0, 2, 1};
int[] starts = {0, 0, 0};
int[] steps = {1, -1, 2};
NDimensionalStepper nds = new NDimensionalStepper(limits, order, starts, steps);
do {
System.out.println(nds);
} while (nds.step());
}
public static void main(String args[]) {
new Test().test();
}
public static class NDimensionalStepper {
// The current positions in each dimension.
// Note that i[order[0]] is the fastest mover.
final int[] i;
// Starts.
final int[] starts;
// Steps.
final int[] steps;
// Limits.
final int[] limits;
// Order.
final int[] order;
// The (unordered) dimension we last stepped.
int d = 0;
// Full constructor.
public NDimensionalStepper(int[] limits, int[] order, int[] starts, int[] steps) {
// Should parameter check to ensure all are the same length.
// Should also check that each dimension will terminate.
this.i = Arrays.copyOf(starts, starts.length);
this.starts = Arrays.copyOf(starts, starts.length);
this.steps = Arrays.copyOf(steps, steps.length);
this.limits = Arrays.copyOf(limits, limits.length);
this.order = Arrays.copyOf(order, order.length);
}
// Default steps to 1.
public NDimensionalStepper(int[] limits, int[] order, int[] starts) {
this(limits, order, starts, defaultSteps(limits, starts));
}
// Default steps - 1 Towards limits.
private static int[] defaultSteps(int[] limits, int[] starts) {
int[] steps = new int[limits.length];
for (int i = 0; i < limits.length; i++) {
// Step towrds limits.
steps[i] = (int) Math.signum(limits[i] - starts[i]);
}
return steps;
}
// Default starts to 0.
public NDimensionalStepper(int[] limits, int[] order) {
this(limits, order, defaultStarts(limits.length));
}
// Default starts - 0, 0, ...
private static int[] defaultStarts(int d) {
int[] starts = new int[d];
Arrays.fill(starts, 0);
return starts;
}
// Default order to normal.
public NDimensionalStepper(int[] limits) {
this(limits, defaultOrder(limits.length));
}
// Default order - ..., 1, 0
private static int[] defaultOrder(int d) {
int[] order = new int[d];
for (int i = 0; i < d; i++) {
order[i] = d - i - 1;
}
return order;
}
// Get the current position in dimension d.
public int get(int d) {
return i[d];
}
// Take just one step. Return false if cant.
public boolean step() {
boolean stepped = false;
boolean finished = false;
while (!stepped && !finished) {
// Which dimension should be stepped (depends on order).
int o = order[d];
// Can we step in the current dimension?
while (finished(o) && d < order.length - 1) {
// Reached a limit! - Move up one dimension.
o = order[++d];
}
if (d < order.length && !finished(o)) {
// Step it.
i[o] += steps[o];
stepped = true;
// Zero all lower dimensions.
while (d > 0) {
d -= 1;
i[order[d]] = starts[order[d]];
}
} else {
// Got to the last without finding one below limit. Finished!
finished = true;
}
}
return !finished;
}
// Equal or passed the limits.
private boolean finished(int o) {
int sign = (int) Math.signum(steps[o]);
return sign * (i[o] + steps[o]) >= sign * limits[o];
}
#Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append("{");
for (int d = 0; d < order.length; d++) {
s.append(get(d));
if (d < order.length - 1) {
s.append(",");
}
}
s.append("}");
return s.toString();
}
}
}
My tests of the equivalents of your three scenarios look like:
private void testBuild1(Build build) {
System.out.println("Build: x,y,z");
for (int x = 0; x < build.getWidth(); x++) {
for (int y = 0; y < build.getHeight(); y++) {
for (int z = 0; z < build.getLength(); z++) {
System.out.println("{" + x + "," + y + "," + z + "}");
}
}
}
int[] limits = {build.getWidth(), build.getHeight(), build.getLength()};
testNDS(new NDimensionalStepper(limits));
}
private void testBuild2(Build build) {
System.out.println("Build: z,y,x");
for (int z = 0; z < build.getLength(); z++) {
for (int y = 0; y < build.getHeight(); y++) {
for (int x = 0; x < build.getWidth(); x++) {
System.out.println("{" + x + "," + y + "," + z + "}");
}
}
}
int[] limits = {build.getWidth(), build.getHeight(), build.getLength()};
int[] order = {0,1,2};
testNDS(new NDimensionalStepper(limits, order));
}
private void testBuild3(Build build) {
System.out.println("Build: x--,y,z");
for (int x = build.getWidth(); x > 0; x--) {
for (int y = 0; y < build.getHeight(); y++) {
for (int z = 0; z < build.getLength(); z++) {
System.out.println("{" + x + "," + y + "," + z + "}");
}
}
}
int[] limits = {0, build.getHeight(), build.getLength()};
int[] order = {2,1,0};
int[] starts = {build.getWidth(), 0, 0};
int[] steps = {-1, 1, 1};
testNDS(new NDimensionalStepper(limits, order, starts, steps));
}
private void testNDS(NDimensionalStepper nds) {
System.out.println("--nds--");
do {
System.out.println(nds);
} while (nds.step());
}
You said depending on user input the order of loop changes. The logic for handling user input will have to be written.
You can code like this:
//Code to populate XInit, XEnd, YInit, YEnd, ZInit, ZEnd based on user input
for(int x = XInit; x < XEnd; x=XInit<XEnd?x+1:x-1){
for(int y = YInit; y < YEnd; y=YInit<YEnd?y+1:y-1){
for(int z = ZInit; z < ZEnd; z=ZInit<ZEnd?z+1:z-1){
//do stuff
}
}
}
Note: You may even want to abstract the calculation of XInit, XEnd etc. parameters in a separate method.
Your "stuff" is likely accessing the values of x, y, and z, so the way you're hard-coding is probably the easiest to follow. Your method names could clearly indicate the ordering. For the three examples you gave, it would look similar to:
public void somethingXYZ(Build build, Stuff stuff) {...}
public void somethingZYX(Build build, Stuff stuff) {...}
public void somethingXnYZ(Build build, Stuff stuff) {...}
When you're coding and want to select one of those methods, your IDE will even help you by listing the available options for that class. I think the way you're organizing it would already work well.

Categories