Getting 2D pixels location

edited September 2014 in Programming Questions

Hi there, let's say I have an image with some red pixels and some blue. I'm creating two arrays, one for red pixels and one for the blue. For each red pixel I want to calculate the distance to each of the blue pixels. The problem is that processing stores pixels in 1D using this common formula "int loc = x + y*img.width", so I'm not able to use the dist() function as it takes 4 args. I was wondering if there's a method to assign each pixel a 2D location.

«1

Answers

  • Answer ✓

    What you want is to get the x and y locations for a pixel in the array

    x = loc % img.width;

    y = loc / img.width;

  • That's after calculating the 1D location? Or instead of that?

  • Here's my code, I'm having difficulties on how to calculate the distance between each pixels in the arrays:

    // Declaring a variable of type PImage
    PImage img;  
    
    void setup() {
      size(800,800);
      img = loadImage("texture_to_fix.jpg");
      //img.resize(800,800);
    }
    
    
    void draw() {
      loadPixels();
      img.loadPixels();
    
      ArrayList<Integer> blackPix;
      ArrayList<Integer> colouredPix;
    
      blackPix = new ArrayList<Integer>();
      colouredPix = new ArrayList<Integer>();
    
      //loop through every pixel
      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;
        x = loc % img.width;
        y = loc / img.width;
    
        // Get the R,G,B values from image
        float r = red   (img.pixels[loc]);
        float g = green (img.pixels[loc]);
        float b = blue  (img.pixels[loc]);
    
        if (r < 30){
          if (g < 30){
            if (b < 30){
          blackPix.add(loc);
            }
          }
        }
        else{ colouredPix.add(loc);}
    
        for (int i : blackPix) {
          for (int c : colouredPix) {
            int dist = dist(blackPix(x), blackPix(y), colouredPic(x), colouredPix(y));
          }
        }
    
      }
    }
    updatePixels();
    image(img,0,0);
    }
    

    I'm obviously getting an error when calculating the distance as "blackPix(x), ecc." is not a function, but I can't figure out how to make it work.

  • edited September 2014 Answer ✓

    replace line 46

        int dist = dist(blackPix(x), blackPix(y), colouredPic(x), colouredPix(y));
    

    with

    float xBlack = i % img.width;
    float yBlack = i / img.width;
    
    float xRed = c % img.width;
    float yRed = c / img.width;
    
    int dist = dist(xBlack, yBlack, xRed, yRed );
    

    not tested.

    Chrisir

  • edited September 2014

    Actually you should have these lines:

    for (int i : blackPix) {
          for (int c : colouredPix) {
         .............
    

    outside the main double for-loop

    otherwiese it could get very time consuming....

  • BTW you have the initial x and y

    and convert it to loc and now you want to convert it back...

    instead you could also store x,y in the ArrayLists using PVector. See reference.

  • If a put this lines i get "cannot convert from float to int"

        float xBlack = i % img.width;
        float yBlack = i / img.width;
    
        float xRed = c % img.width;
        float yRed = c / img.width;
    
        int dist = dist(xBlack, yBlack, xRed, yRed );
    
  • float dist = dist(xBlack, yBlack, xRed, yRed );
    
  • I saw that coming ;-)

  • BTW i've seen the reference but didn't really understand how to use PVector in my code

  • Changing dist to float does not give error but my output image is completely blank, weird.

  • you don't perform any changes on img ...

  • also line 52 should probably be

    img.updatePixels(); 
    
  • edited September 2014

    Also remember that draw() runs 60 times per sec

    you can stop that by saying noLoop(); somewhere

    otherwise he will change your img on and on fast....

  • just to test your loading you might want to try

    PImage img; 
    
    void setup() {
      size(800,800);
      img = loadImage("texture_to_fix.jpg");
      //img.resize(800,800);
    }
    
    
    void draw() {
    image(img,0,0);
    exit();  
    
    // leave the rest here ......................
    
  • so it should show me the original image,isn't it?

  • remember the name of the file must be perfectly the same as on your hard drive (even small / capital letters)

  • so it should show me the original image,isn't it?

    yes

  • It doesn't work changing does lines in void draw(). If I remove the for loop which calculates the distance my image shows up, so it's not a filename problem. BTW here's my new code:

    // Declaring a variable of type PImage
    PImage img; 
    
    void setup() {
      size(800,800);
      img = loadImage("texture_to_fix.jpg");
      //img.resize(800,800);
    }
    
    
    void draw() {
      image(img,0,0);
      exit();  
    
    
      ArrayList<Integer> blackPix;
      ArrayList<Integer> colouredPix;
    
      blackPix = new ArrayList<Integer>();
      colouredPix = new ArrayList<Integer>();
    
      //loop through every pixel
      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;
        x = loc % img.width;
        y = loc / img.width;
    
        // Get the R,G,B values from image
        float r = red   (img.pixels[loc]);
        float g = green (img.pixels[loc]);
        float b = blue  (img.pixels[loc]);
    
        if (r < 30){
          if (g < 30){
            if (b < 30){
          blackPix.add(loc);
            }
          }
        }
        else{ colouredPix.add(loc);}
      }
    }
    
    for (int i : blackPix) {
          for (int c : colouredPix) {
            float xBlack = i % img.width;
            float yBlack = i / img.width;
    
            float xCol = c % img.width;
            float yCol = c / img.width;
    
            float dist = dist(xBlack, yBlack, xCol, yCol);
          }
        }
    updatePixels();
    image(img,0,0);
    }
    

    Maybe there's some logic problem

  • well, as said, you don't change the img at all, you just do some detections and calc a dist....

    also, in line 58 you should refer to the image

  • edited September 2014

    this works and gives you a pink pixel instead of a black one when the dist < 170.

    but, I had to change line 36 and 37 (<130) because no pixel was added to blackPix before... you can see this when you println the size of the ArrayLists with

    println (blackPix.size() + " " +colouredPix.size() );

    here is the sketch

    // Declaring a variable of type PImage
    PImage img; 
    
    ArrayList<Integer> blackPix;
    ArrayList<Integer> colouredPix;
    
    void setup() {
      size(800, 800);
      img = loadImage("texture_to_fix.jpg");
      //img.resize(800,800);
      frameRate(1);
    }
    
    
    void draw() {
    
      // test - works!
      //  image(img, 0, 0);
      //  exit();  
    
    
    
    
      blackPix = new ArrayList<Integer>();
      colouredPix = new ArrayList<Integer>();
    
      //loop through every pixel
      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;
          x = loc % img.width;
          y = loc / img.width;
    
          // Get the R,G,B values from image
          float r = red   (img.pixels[loc]);
          float g = green (img.pixels[loc]);
          float b = blue  (img.pixels[loc]);
    
          if (r < 130) {
            if (g < 130) {
              if (b < 30) {
                blackPix.add(loc);
              }
            }
          }
          else { 
            colouredPix.add(loc);
          }
        }//for
      }//for
    
      println (blackPix.size() + "    " +colouredPix.size() ); 
      for (int i : blackPix) {
        for (int c : colouredPix) {
          float xBlack = i % img.width;
          float yBlack = i / img.width;
    
          float xCol = c % img.width;
          float yCol = c / img.width;
    
          float dist = dist(xBlack, yBlack, xCol, yCol);
          if (dist<170) {
            color pink = color(255, 102, 204);
            img.pixels[ img.width*int(yBlack) + int(xBlack)  ] = pink;
          }
        }//for
      }//for
    
      img.updatePixels();
      image(img, 0, 0);
    }
    //
    
  • edited September 2014

    there is also a logic flaw in this

    else {
            colouredPix.add(loc);
          }
    

    this applies already when not if (r < 130) {

    but you want it to apply when all 3 previous conditions are false, not only the first...

  • since the change is applied to the image and then saved and applied again to it and on and on, we only have a change in the first round, then it stops (and both ArrayLists stay empty)

    you could change this, when you insert instead of pink e.g. red (i.e. something that gets detected the next time draw runs)

  • Thanks Chrisir, but using your sketch the output image is gray. If I try to remove this line: frameRate(1); in void setup(), output image is white, I'm wonder how. BTW my aim with this sketch was to put all the black pixels into an array and coloured ones into another, then for each item in blackpixels[] calculate the distance to each item in coloured[] and choose the pixel with the minimum distance (using the min() function), the closest one basically. Then replace every black pixel to its closest coloured pixel. Am i setting up the sketch in the right way?

  • edited September 2014

    you wrote

    sketch the output image is gray. If I try to remove this line: frameRate(1); in void setup(), output image is white, I'm wonder how

    well, the frameRate slows the sketch down to 1 screen update per sec, so you need to wait 2-4 secs

    It works with my test image, when you have another image, you maybe need to adjust the values for detection (130,130,30 in the if and 170 in the dist()) to make it work.

    • Your approach is ok, but you are not done yet. E.g. you don't choose the pixel with the minimum distance (using the min() function) and you don't change any pixel yet.
  • edited September 2014

    Thanks, that actually worked but I had to wait something like a minute. BTW here's how I'm proceding to replace blackpixels to closest color:

    // Declaring a variable of type PImage
    PImage img;
    
    ArrayList<Integer> blackPix;
    ArrayList<Integer> colouredPix;
    
    void setup() {
      size(800, 800);
      img = loadImage("texture_to_fix.jpg");
      //img.resize(800,800);
      frameRate(1);
    }
    
    
    void draw() {
    
      // test - works!
      //  image(img, 0, 0);
      //  exit(); 
    
    
    
    
      blackPix = new ArrayList<Integer>();
      colouredPix = new ArrayList<Integer>();
    
      //loop through every pixel
      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;
          x = loc % img.width;
          y = loc / img.width;
    
          // Get the R,G,B values from image
          float r = red   (img.pixels[loc]);
          float g = green (img.pixels[loc]);
          float b = blue  (img.pixels[loc]);
    
          if (r < 130) {
            if (g < 130) {
              if (b < 30) {
                blackPix.add(loc);
              }
            }
          }
          else {
            colouredPix.add(loc);
          }
        }//for
      }//for
    
      println (blackPix.size() + "    " +colouredPix.size() );
      for (int i : blackPix) {
        for (int c : colouredPix) {
          float xBlack = i % img.width;
          float yBlack = i / img.width;
    
          float xCol = c % img.width;
          float yCol = c / img.width;
    
          FloatList distList;
          distList = new FloatList();
          distList.append(dist(xBlack, yBlack, xCol, yCol));
    
          float[] dist = distList.array();
          float closestPix = min(dist); 
          color closestCol = color(img.pixels[closestPix]);
    
          for (int v : blackPix) {
            img.pixels[ img.width*int(yBlack) + int(xBlack)  ] = closestCol;
          }
    
        }//for
      }//for
    
    
    
      img.updatePixels();
      image(img, 0, 0);
    }
    //
    

    but I'm obviously getting the error here ("cannot converto from float to int"): color closestCol = color(img.pixels[closestPix]);

  • edited September 2014

    I think that's wrong: lines 63 to 73

  • edited September 2014

    color closestCol = color(img.pixels[ int (closestPix) ]);

    but the overall design is wrong for lines 63 to 73

  • any hints??

  • let me see

  • edited September 2014

    you want to find out for each blackpixel the closest coloured pixel

    for each blackpixel you loop over all coloured pixels to find the closest....

    shouldn't you therefore put

      FloatList distList;
      distList = new FloatList();
    

    before the inner for-loop

    and evaluate which one is closest (min) from the list after you completed building the list distList (after closing the inner for-loop) and not for every coloured pixel?

    I mean you need to build the complete list before it is worth to check which element is the smallest dist....

    (it reminds me of a previous bug you had in your 1st version ;-) )

  • Mmm..I'll give it a try

  • I've tried following your hints, but I'm a bit stacked as I'm still looping trough the coloured pixels, otherwise "xCol" and "yCol" variable are not defined, can't find a different way of doing it. The result is that I'm getting black pixels replaced by coloured ones, but not really as expected. Here's my new code:

    // Declaring a variable of type PImage
    PImage img;
    
    ArrayList<Integer> blackPix;
    ArrayList<Integer> colouredPix;
    float xBlack = 0;
    float yBlack = 0;
    float xCol = 0;
    float yCol = 0;
    
    void setup() {
      size(800, 800);
      img = loadImage("texture_to_fix.jpg");
      //img.resize(800,800);
      frameRate(1);
    }
    
    
    void draw() {
    
      // test - works!
      //  image(img, 0, 0);
      //  exit(); 
    
    
    
    
      blackPix = new ArrayList<Integer>();
      colouredPix = new ArrayList<Integer>();
    
      //loop through every pixel
      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;
          x = loc % img.width;
          y = loc / img.width;
    
          // Get the R,G,B values from image
          float r = red   (img.pixels[loc]);
          float g = green (img.pixels[loc]);
          float b = blue  (img.pixels[loc]);
    
          if (r < 130) {
            if (g < 130) {
              if (b < 30) {
                blackPix.add(loc);
              }
            }
          }
          else {
            colouredPix.add(loc);
          }
        }//for
      }//for
    
      println (blackPix.size() + "    " +colouredPix.size() );
    
    
      for (int i : blackPix) {
        FloatList distList;
        distList = new FloatList();
    
        for (int c : colouredPix) {
    
          xBlack = i % img.width;
          yBlack = i / img.width;
    
          xCol = c % img.width;
          yCol = c / img.width;
    
        }//for
    
        distList.append(dist(xBlack, yBlack, xCol, yCol));
        float[] dist = distList.array();
        float closestPix = min(dist); 
        color closestCol = color(img.pixels[int (closestPix)]);
    
        img.pixels[ img.width*int(yBlack) + int(xBlack)  ] = closestCol;
      }//for
    
    img.updatePixels();
    image(img, 0, 0);
    }
    //
    
  • i've changed the image used for testing to better figure out the results and realized that the code is working but is replacing the color of the first black pixels and then calculating the closest coloured again (which results to be the previous black pixel turned into coloured). Here you can see the source image on the left and the result on the right. output

  • I've put the line to replace pixels' colour outside the loop, but the output image is the same as the source, nothing changes.

    for (int i : blackPix) {
        FloatList distList;
        distList = new FloatList();
    
        for (int c : colouredPix) {
    
          xBlack = i % img.width;
          yBlack = i / img.width;
    
          xCol = c % img.width;
          yCol = c / img.width;
    
        }//for
    
        distList.append(dist(xBlack, yBlack, xCol, yCol));
        float[] dist = distList.array();
        float closestPix = min(dist); 
        closestCol = color(img.pixels[int (closestPix)]);
    
      }//for
    img.pixels[ img.width*int(yBlack) + int(xBlack)  ] = closestCol;
    
    img.updatePixels();
    image(img, 0, 0);
    }
    
  • line 13 must be after line 15

  • you need to build the complete list in the inner for-loop

  • I've put the distList.append() in the inner for loop, but the resulting image is the same as the source

  • please enter the line noLoop() into draw()

    we want to run it only once

  • 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;
        x = loc % img.width;
        y = loc / img.width;
    

    why are you setting x and y here? you already have their values from the loop counters. at best these lines do nothing...

  • I am afraid the approach won't work

    you're mixing up things a little...

    you store the dist and take the smallest dist

    but then suddenly you use the smallest dist (!) as a index (!) for pixels... won't work

  • edited September 2014

    instead of line 12

    distList.append(dist(xBlack, yBlack, xCol, yCol));

    better compare the dist dist(xBlack, yBlack, xCol, yCol)

    to the previous smallest dist smallestDist ( just use < )

    if smaller, set smallestDist to dist(xBlack, yBlack, xCol, yCol)

    and store xBlack, yBlack into xBlackForSmallestDist, yBlackForSmallestDist

    and store xColoured, yColoured into xColouredForSmallestDist, yColouredForSmallestDist

    and use this later on...

    • Thus you can say something useful in line 21 (which should be in line 19 btw)
  • you really need to dive into this...

    breathe the code...

  • is that correct:

    • you want to find out for each blackpixel the closest coloured pixel and replace the black pixel with the color of this closest coloured pixel ?
  • Yes that's correct

  • the thing is when you use min on you list of floats it returns that value but not the index. Therefore you can not retrieve the screen coords from another array.

    Therefore I advised you to do a different approach....

    see above

  • You mean something like this in the inner for loop?

    if ((dist(xBlack, yBlack, xCol, yCol) < (min(dist(xBlack, yBlack, xCol, yCol))) {
            float smallestDist = dist(xBlack, yBlack, xCol, yCol);
            xBlackForSmallestDist.append(xBlack);
            yBlackForSmallestDist.append(yBlack);
            xColForSmallestDist.append(xCol);
            yColForSmallestDist.append(yCol);
    
  • Obviously I've previously declared the lists

  • more like this...

    if ( dist(xBlack, yBlack, xCol, yCol) < smallestDist ) {
            float smallestDist = dist(xBlack, yBlack, xCol, yCol);
            xBlackForSmallestDist = xBlack;
            yBlackForSmallestDist = yBlack;
            xColForSmallestDist  = xCol;
            yColForSmallestDist = yCol;
    
  • edited September 2014

    the idea is not to keep a list of all dist and take the smallest after the for-loop

    but instead compare throughout in the for-loop and just take the smallest dist value by comparison AND the 2 pixels that belong to this dist

    when you come out of the for-loop the vars xBlackForSmallestDist yBlackForSmallestDist etc. hold the right values

Sign In or Register to comment.