Problem looping through a 4D array

Hello! I want to create an animation based on the greyscale values (0-255) of each pixel in an image.

Each pixel in the image would be represented by drawing a ‘grid’ (say, 4 rows * 3 columns) of squares (rects), with each square in the grid being ON (white) or OFF (black), where the number of squares ON represents the greyscale value of that pixel.


grid


There are MANY permutations of (for example) 3 squares ON in a 12 pixel grid:

E.g. greyValue = 3 (3 squares ON):

{{1, 1, 1}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // greyVal[2] - 3 pixels ON
{{0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 0, 0}}, 
{{0, 0, 1}, {0, 0, 0}, {0, 0, 0}, {1, 1, 0}} etc...

I want to ‘animate’ each grid, so that for each particular greyscale value, the corresponding grid will loop and draw ALL of the permutations in the corresponding sub-array.

Where I’m at:
I’ve generated some values in a python script - only a very few shown here as an example.

I’m trying to draw a couple of these grids, with each grid looping through ALL the configurations in the array for each grid’s particular greyValue.

I get ArrayIndexOutOfBoundsException if I try to draw separate grids with different greyValues and therefore different numbers of pixelConfigurations.

I see that pixelConfiguration is being incremented each time draw_single_grid is called, and its value is being ‘shared’ by each call… How do I get each grid to draw all its configurations in sequence, independently of any other grid…?

Any insight as to where I’m getting mixed up gratefully received!. Thanks :slight_smile:


int pixelSize = 12;
int pixelGap = 2;

int greyValue;
int pixelConfiguration = 0;

void draw() {
  frameRate(1);

  draw_single_grid(20, 20, greyValue = 1);
  draw_single_grid(80, 20, greyValue = 0);
  
  println("draw____");
}

void draw_single_grid(int xOffset, int yOffset, int greyVal) {

  for(int row = 0; row < allPixelConfigs[greyVal][0].length; row++) {
    for(int column = 0; column < allPixelConfigs[greyVal][0][0].length; column++) {

      int fillNum = allPixelConfigs[greyVal][pixelConfiguration][row][column] * 255; 
      fill(fillNum);

      int xPos = (column * pixelSize + xOffset) + (column * pixelGap);
      int yPos = (row * pixelSize + yOffset) + (row * pixelGap);

      rect(xPos, yPos, pixelSize, pixelSize);
    }
  }

  pixelConfiguration++;
  println("pixelConfiguration: ", pixelConfiguration);
 

  if (pixelConfiguration >= allPixelConfigs[greyVal].length) {
    pixelConfiguration = 0;
    println("repeat");
  }
}


int[][][][] allPixelConfigs = {         // (4 rows x 3 columns) = 12 pixel grid 
  // some configurations of pixels ON

  {
    {{1, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // greyVal[0] - 1 pixel ON 
    {{0, 1, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
    {{0, 0, 1}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
    {{0, 0, 0}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}
  }, 


  {
    {{1, 1, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // greyVal[1] - 2 pixels ON
    {{1, 0, 0}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
    {{0, 0, 0}, {0, 0, 0}, {0, 1, 1}, {0, 0, 0}}
  }, 


  {
    {{1, 1, 1}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // greyVal[2] - 3 pixels ON
    {{1, 1, 0}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
    {{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}}, 
    {{1, 0, 1}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
    {{0, 0, 0}, {1, 0, 0}, {0, 1, 1}, {0, 0, 0}}, 
    {{1, 0, 1}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
    {{0, 0, 0}, {0, 0, 0}, {1, 1, 1}, {0, 0, 0}}
  }, 


  {
    {{1, 1, 1}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // greyVal[3] - 4 pixels ON 
    {{0, 0, 0}, {0, 0, 1}, {1, 1, 1}, {0, 0, 0}}
  }, 

};

This is further to my earlier question, beautifully answered by @tony and @Chrisir - although I don’t think I’ve fully grokked how draw() actually works yet!


1 Like

Hi @timAllan,

I don’t understand the whole scheme but just focusing on one 4 * 3 grid I’d like to throw in a couple of ideas. For 3 bits set there are 12 * 11 * 10 = 1320 possibilities of which bits are selected for the 1st, 2nd, 3rd. But the bits are equivalent and patterns are repeated so divide by 6 = 220. It’s not practical to code all those patterns with {0, 0, 1} etc. so you need to generate them. I know of two methods.

  • count from 0 to 4095 (2^12-1), convert to binary, count the bits set, if 3 that’s a good pattern.

  • Use a recursive function to select bit 0 low and high. For each of those recurs to select bit 1 lo/hi etc. At each level assess whether there are 3 bits set. This is more complicated to code, but has the advantage that once you have 3 bits set you don’t have to complete the recursion. In many situations this is quicker.

If either looks interesting I can give you code.

1 Like

Hi @RichardDL

Thanks for your advice and methods on generating values for combinations of pixels.

I’d be very interested to see your method in code, as the methods you suggest would be far more efficient than either of mine below. Thanks!

Being a novice coder and even more of a novice when it comes to mathematics, I thought this would be an interesting project to learn some coding (and some math).

I’ve written a couple of Python scripts to generate values, linked below (one is a brute-force method( I had to learn a bit about permutations and combinations so as to limit the search space), and the other uses the Python built-in permutation() function… Both are very much beginners code and still in-progress but I’m having fun!

https://gist.github.com/timothy-allan/f2af5042c8793868ddf638eab4afa198

https://gist.github.com/timothy-allan/cf73d0769d467ab55bff26390608777b

1 Like

Here, when I just have the code directly in the draw() loop, with different variable names for the pixelConfiguration ‘iterators’ all is fine.

Whenever I try to generalise this into a function, I get into the same trouble. Halp! :wink:

int pixelSize = 12;
int pixelGap = 2;

int greyValue;
int pixelConfigurationONE;
int pixelConfigurationTWO;


void draw() {
  frameRate(1);

  greyValue = 1;
  for (int row = 0; row < allPixelConfigs[greyValue][0].length; row++) {
    for (int column = 0; column < allPixelConfigs[greyValue][0][0].length; column++) {

      int fillNum = allPixelConfigs[greyValue][pixelConfigurationONE][row][column] * 255; 
      fill(fillNum);

      int xPos = (column * pixelSize + 10) + (column * pixelGap);
      int yPos = (row * pixelSize + 10) + (row * pixelGap);

      rect(xPos, yPos, pixelSize, pixelSize);
    }
  }

  pixelConfigurationONE++;
  if (pixelConfigurationONE >= allPixelConfigs[greyValue].length) {
    pixelConfigurationONE = 0;
  }


  greyValue = 0;
  for (int row = 0; row < allPixelConfigs[2][0].length; row++) {
    for (int column = 0; column < allPixelConfigs[greyValue][0][0].length; column++) {

      int fillNum = allPixelConfigs[greyValue][pixelConfigurationTWO][row][column] * 255; 
      fill(fillNum);

      int xPos = (column * pixelSize + 80) + (column * pixelGap);
      int yPos = (row * pixelSize + 10) + (row * pixelGap);

      rect(xPos, yPos, pixelSize, pixelSize);
    }
  }

  pixelConfigurationTWO++;

  if (pixelConfigurationTWO >= allPixelConfigs[greyValue].length) {
    pixelConfigurationTWO = 0;
  }
}


void draw_single_grid(int xOffset, int yOffset, int greyVal) { 
}

int[][][][] allPixelConfigs = {         // (4 rows x 3 columns) = 12 pixel grid 
  // some configurations of pixels ON

  {
    {{1, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // greyVal[0] - 1 pixel ON 
    {{0, 1, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
    {{0, 0, 1}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
    {{0, 0, 0}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}
  }, 

  {
    {{1, 1, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // greyVal[1] - 2 pixels ON
    {{1, 0, 0}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
    {{0, 0, 0}, {0, 0, 0}, {0, 1, 1}, {0, 0, 0}}

  }, 

  {
    {{1, 1, 1}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // greyVal[2] - 3 pixels ON
    {{1, 1, 0}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
    {{0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {0, 0, 1}}, 
    {{1, 0, 1}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
    {{0, 0, 0}, {1, 0, 0}, {0, 1, 1}, {0, 0, 0}}, 
    {{1, 0, 1}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
    {{0, 0, 0}, {0, 0, 0}, {1, 1, 1}, {0, 0, 0}}
  }, 

  {
    {{1, 1, 1}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // greyVal[3] - 4 pixels ON 
    {{0, 0, 0}, {0, 0, 1}, {1, 1, 1}, {0, 0, 0}}
  }, 
};
1 Like

My advice would be to use a 2D array of a new class PixelRepresentation

In the class you would have another 2D array representing the 6 fields of each pixel

Much easier to use

Hello,

I needed to change this so it would run:

int pixelConfigurationONE;
int pixelConfigurationTWO;

:)

1 Like

Well spotted @glv - thanks!

1 Like

Simplifying my code would certainly be a good step forward!

I haven’t done a lot with classes - I know I have to define a new class pixelRepresentation {} and use a constructor maybe like myPixelRepresentation[][] = new pixelRepresentation, but I’m not sure how to actually implement a class for my code/data…

Could you give me an outline of how I would set it up as you suggest? I’m also not sure what you mean by the ‘the 6 fields of each pixel’ - do you mean these ‘inner’ parts of my 4D array (‘greyVals’):

 {
    {{1, 1, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // greyVal[1] - 2 pixels ON
    {{1, 0, 0}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}},
    {{0, 0, 0}, {0, 0, 0}, {0, 1, 1}, {0, 0, 0}}
  }, 

  {
    {{1, 1, 1}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}}, // greyVal[2] - 3 pixels ON
    {{1, 1, 0}, {1, 0, 0}, {0, 0, 0}, {0, 0, 0}}, 
  }

etc?

For a 12 pixel grid, there would be 13 ‘greyVals’ - inclusive of all pixels ON and all OFF - each holding a varying number of configurations - e.g. 924 for 6 pixels ON in a 12-pixel grid.

Appreciate your help so far, cheers!

I should also say the data doesn’t HAVE to be in exactly the format I’ve used (list of greyVals, several lists of rows and columns per greayVal) - I created the data that way in Python as I thought it would be a good way to represent and iterate through all these values… You’ve suggested I break that apart anyway by creating a class or number of classes so I’m happy to ditch that format if there is a better, simpler way to represent the data…

@RichardDL has provided a really effective and simple bit of code that returns the values as follows:

  000000000111
  000000001011
  000000001101
...
1 Like

Here’s the counting way of finding all 12 bit numbers with 3 bits set. Total of 220 as expected. (The recursive way will take me longer. I can’t play today.)

boolean is3bits(int no)
{
  // return true if given number has 3 bits set.
  int ix;
  int tot = 0;
  for (ix = 0; ix < 12; ix++)
  {
    if ((no & 1) == 1){tot++;}
    no = no >> 1;
  }
  return tot == 3;
}

void setup()
{
  int num;
  int count3 = 0;
  for (num  = 0; num < 4096 ; num++)
  {
    if (is3bits(num))
    {
      count3++;
      println(String.format("%4d %s %4d", num, binary(num, 12), count3));
    }
  }
}
void draw(){}

Output:

Dec. Bin.         Count

   7 000000000111    1
  11 000000001011    2
  13 000000001101    3
  14 000000001110    4
...
3136 110001000000  217
3200 110010000000  218
3328 110100000000  219
3584 111000000000  220
2 Likes

Wow, thanks @RichardDL - thats great, thank you. It does in a few lines what I’ve spent a good few days of head-scratching and MANY lines of code to do far less efficiently!

I’ll have a good look through that to make sure I understand it. I’ve been reading a bit around bitwise operations… :slight_smile:

A good way to make sure I understand a piece of code works is to try and ‘port’ it to a different language. I’ll give it a go with python and let you know the results.

here is my way to split your 4D array into a simpler structure with two 2D grids using a class


PImage img;

PixelRepresentation[][] myList; 

void setup() {
  size(1600, 1600);
  noSmooth(); 

  img=loadImage("IMG_2289.jpg");
  img.resize (200, 200);
  myList = new PixelRepresentation[img.width][img.height];


  for (int x = 0; x < img.width; x++) {
    for (int y = 0; y < img.height; y++ ) {
      // Calculate the 1D pixel location
      int loc = x + y*img.width;

      color c1=img.pixels[loc]; 

      myList[x][y] = new PixelRepresentation (c1, x*5, y*5);
    }
  }
}

void draw() {
  //image(img, 10, 10);

  for (int x = 0; x < img.width; x++) {
    for (int y = 0; y < img.height; y++ ) {
      myList[x][y].display();
    }
  }
}

// ================================================================

class PixelRepresentation {

  //
  color col;
  float x, y;

  boolean[][] fieldArray = new boolean [ 2 ][ 3 ]; // !!!!!!!!!!!!  

  PixelRepresentation (color col_, 
    float x_, float y_) {
    col=col_;
    x=x_;
    y=y_;
  }

  void display() {
    // use fieldArray here 
    fill(col); 
    stroke(col); 
    //    point(x, y);
    rect(x, y, 
      3, 3 );
  }
}//class
//
2 Likes

I mean this:

(sorry, 12 fields)

Chrisir

1 Like

OK, great!

There’s quite a lot there to take in so give me some time to go through it, but that looks awesome… Thank you.

1 Like

There are a tutorials about

2 Likes

here is another data structure to represent

  • an array that takes gray scales as an index and contains objects of type class ListOfPermuts
  • In the class ListOfPermuts there is an ArrayList of the permutations for this brightness, stored as an ArrayList of type class Field
  • The class Field is a field of 3x4. It can make the animation

Chrisir


// list uses gray scales as index. The list contains the different permuts for this gray scale (number of permuts can vary).
ListOfPermuts[] permuts = new ListOfPermuts[256];   


void setup() {
  size(1600, 800);
  noSmooth();

  // fill permuts 
  // i represents gray value 
  for (int i=0; i<256; i++) {
    permuts[i] = new  ListOfPermuts( i );
  }//for 

  background(0);

  // make the rects in the upper area 
  for (int i=0; i<100; i++) {
    fill(random(255), random(255), random(255));
    rect(i*32, 3, 
      30, 30);
  }//for
  //
}//setup

void draw() {

  fill(0);
  rect(0, 33, 
    width, height);

  color c1 = get(mouseX, mouseY); 

  fill(255, 0, 0); 
  text("Please place the mouse on the rects to test different brightness values \nbrightness "  
    + brightness(c1), 
    100, 100);

  permuts[int(brightness(c1))].display(); 
  //
} // func 

//================================================================

class ListOfPermuts {

  // class holds all permutations for ONE brighntness 

  // length can vary (therefore ArrayList)
  ArrayList<Field> listOfPermutations = new ArrayList();

  int brightnessOfField; 

  // counter or permut for display as an animation
  int permutNum;

  //constr 
  ListOfPermuts (int brightnessOfField_) {

    brightnessOfField = 
      brightnessOfField_;

    // random number of permuts for this brightness.....
    // replace by something that gives the permuts for the brightnessOfField_
    for (int i=0; i<random (1, 24); i++) {
      Field f1 = new Field(brightnessOfField);
      listOfPermutations.add(f1);
    } //for
  } // constr ---------

  void display() {
    int xoffset=330;
    int yoffset=500;

    // text 
    text("Brightness " 
      +brightnessOfField 
      +" has "
      +listOfPermutations.size()
      +" permutations (these are not real data but made up).", 
      xoffset, yoffset-33);

    // make a horizontal list
    for (Field f1 : listOfPermutations) {
      f1.display(xoffset, yoffset); 
      xoffset+=40;
    }//for

    // Animation 
    Field f1 = listOfPermutations.get(permutNum%listOfPermutations.size()); 
    f1.display(330, height-139);
    permutNum++;
    //
  } // func  
  //
} //class 

//================================================================

class Field {

  // ONE 3x4 Field 

  boolean [][] field = new boolean [3][4];  

  //constr 
  Field (int brightnessOfField_) {

    // replace by something that gives the permuts for the brightnessOfField_
    for (int x = 0; x < field.length; x++) {
      for (int y = 0; y < field[0].length; y++ ) {
        field[x][y] = false;
        if (random(100)>50) 
          field[x][y] = true;
      }
    }
  }//constr ---------

  void display( int xoffset, int  yoffset) {
    //
    for (int x = 0; x < field.length; x++) {
      for (int y = 0; y < field[0].length; y++ ) {
        //
        //  noStroke(); 
        stroke(255);
        noFill();
        fill(#F0F507); // Yellow  
        //rect( xoffset-1, yoffset-1, 
        //  10, 20); 

        noStroke();
        fill(0); 
        if (field[x][y])
          fill(255); // WHITE 
        rect(xoffset + x*5, yoffset +  y*5, 
          4, 4);
      }
    }
  }//func
  //
}//class
//
2 Likes

you have to join with the other Sketch

also, you have to fill in something that gives the permuts for the brightnessOfField_

1 Like

Here’s the recursive way to get 3 bit set out of 12

final boolean T = true;
final boolean F = false;

int result_count;

boolean is3bits(int no)
{
  int ix;
  int tot = 0;
  for (ix = 0; ix < 12; ix++)
  {
    if ((no & 1) == 1){tot++;}
    no = no >> 1;
  }
  return tot == 3;
}

void recurs(int num0, int depth)
{
  int    abit;    // this level's bit
  int    num1;    // solution or  pass to next recurs
  
  if (depth < 12)
  {
    // my bit zero and 1
    for (abit = 0; abit < 2; abit++)
    {
      // 'or' this bit into the number
      num1 = num0 | (abit << depth);
      if (is3bits(num1))
      {
        // print the solution
        println(String.format("%4d %s %4d ", num1, binary(num1, 12), ++result_count));
      }
      else
      {
        // continue looking at more bits
        recurs(num1, depth + 1);
      }
    }
  }
}

void setup()
{
  int num;
  int count3 = 0;
  
  if (F) // counting 
  {
    for (num  = 0; num < 4096 ; num++)
    {
      if (is3bits(num))
      {
        count3++;
        println(String.format("%4d %s %4d", num, binary(num, 12), count3));
      }
    }
  }
  
  if (T) // recursing
  {
    result_count = 0;
    recurs(0, 0);
  }
  exit(); 
}

void draw(){}

Output

Dec. Bin.        Count
3584 111000000000    1 
3328 110100000000    2 
2816 101100000000    3 
1792 011100000000    4 
3200 110010000000    5 
...
  35 000000100011  217 
  19 000000010011  218 
  11 000000001011  219 
   7 000000000111  220
3 Likes

@RichardDL – nice recursive solution.

The “three bits” (or sum of three powers of two) integer sequence is here:

A014311 : Numbers with exactly 3 ones in binary expansion.
https://oeis.org/A014311

I looked it up by entering the first ten results separated by commas. The sequence page for A014311 includes a nice little (cryptic) C solution (which I translated into Processing-Java, below). Rather than looping over the search space (4096) and checking each one with is3bits, you can just input the nth number and get it directly.

void setup() {
  // loop
  for (int i=1; i<=10; i++) {
    int num = A014311(i);
    println(String.format("%4d %s %4d", num, binary(num, 12), i));
  }
  // direct
  int i = 220;
  int num = A014311(i);
  println(String.format("%4d %s %4d", num, binary(num, 12), 220));
}

int A014311(int n) {
  if (n == 1) return 7;
  return hakmem175(A014311(n - 1));
}
int hakmem175(int x) {
  int s, o, r;
  s = x & -x;
  r = x + s;
  o = r ^ x;
  o = Integer.divideUnsigned(o >>> 2, s);
  return r | o;
}
   7 000000000111    1
  11 000000001011    2
  13 000000001101    3
  14 000000001110    4
  19 000000010011    5
  21 000000010101    6
  22 000000010110    7
  25 000000011001    8
  26 000000011010    9
  28 000000011100   10
3584 111000000000  220

That said, the C-to-Java is almost inscrutable.
To understand the “same bit count, but bigger value” HAKMEM 175 algorithm that it is built on, see: http://code.iamkate.com/articles/hakmem-item-175/

Or just use it and enjoy.

4 Likes

Thanks, everyone for your amazing input on this - great response from the Processing community :slight_smile: ++

I’ve only just now been able to get back to it, and as a novice, there are a lot of things here that are new to me. I hope I can keep asking questions as I work through it all.


Cool to know I have stumbled upon such a well-researched problem.

1 Like

If your problem can be re-expressed in the form “I need to generate a specific sequence of integers” then a shocking number of such problems are actually well-researched.

If you are working in creative computing and digital art then integer sequences can be one great interface for leveraging interdisciplinary work. Others might care deeply about the same numbers sequence that you do – for reasons that relate not to drawing rectangles but instead to cellular biology, or stock-cutting, or metronomes, et cetera…

1 Like