How to move an image (without redrawing it)

I want to load a large image (PImage img;), change some of the pixels, move the image horizontally or vertically, change some more pixels, move the image again, and so on…

This should be easy, but I can’t find a way to do it.

I know how to move an image. After loading the image, I can change the values of var1 and var2 in draw(). The image will move, BUT this does not meet my needs as any changes I make to the image will only survive for one frame.

void draw() {

  image(img, 90, 80);
  image(img, var1, var2);

}

I tried to use translate(), but again as the image will be in draw() it will only survive for one frame. Same with scroll (class HScrollbar).

What am I missing??

Hello.

If I’m understanding your question correctly, you can use loadPixels() and updatePixels() to update your image, you can use variables to change the position of your image and keep it persistent between frames.

I made a sketch that hopefully illustrates that, if not, feel free to ask follow-up questions!
Sketch

Hello @paulstgeorge,

See example here:

You will have modify to edit the pixels in draw() and move the image with variables.

References:

:)

@prontopablo
OK, thank you, your sketch works perfectly (see below).

But, but, but…
Are you not making all the changes in setup() and then moving the image?
I want, if possible to make some changes to the image, then move it, make some more changes, …

PImage img;
PImage  modifiedImg;
int var1 = 0; // perhaps float
int var2 = 0;
String imagename = "ireland.jpg";

void setup() {
  size(400, 400);

  img = loadImage(imagename);
  // Create a copy of the original image just to show the unmodified image
  modifiedImg = createImage(img.width, img.height, ARGB);
  modifiedImg.copy(img, 0, 0, img.width, img.height, 0, 0, img.width, img.height);
  modifyImage();
}

void draw() {
  background(255);

  // Move the image
  var1 += 1;
  var2 += 1;
  image(img, 0, 0, 200, 200);
  // Display the modified image
  image(modifiedImg, var1, var2, 200, 200);
}

// Sample function just to invert colours (this could be whatever change you wanna make to the image)
void modifyImage() {
  // Apply changes to the copied image
  modifiedImg.loadPixels();
  for (int i = 0; i < modifiedImg.pixels.length; i += 4) {
    modifiedImg.pixels[i] = 255 - modifiedImg.pixels[i]; // Red
    modifiedImg.pixels[i + 1] = 255 - modifiedImg.pixels[i + 1]; // Green
    modifiedImg.pixels[i + 2] = 255 - modifiedImg.pixels[i + 2]; // Blue
  }
  modifiedImg.updatePixels();
}

Hi



@glv
Thanks and hello,
Yes I am happy with pixels, but I want to change pixels (presumably in draw()) and then move the image, then change some other pixels, move it again and so on. None of the examples in the excellent links you sent have both movement and changes that occur after setup(), or have I missed them?

Hi, please check the Sketch I sent again, I’ve updated it to make the changes in draw. You just need to call img.loadPixels(), then modify the image, then call img.updatePixels(). This can be done in draw, too.

The example was to get you started.

I did state:

Example:

// Modified:
// https://processing.org/reference/PImage_pixels.html

PImage tower;

void setup() {
  size(400, 400);
  tower = loadImage("http://learningprocessing.com/code/assets/sunflower.jpg");
  dimension = tower.width * tower.height;
  
  //println(dimension, dimension/60, dimension/60/60);
  println("This will take: ", dimension/60/60, "min");  // Default frameRate 60
  
  //int dimension = tower.width * tower.height;
  //tower.loadPixels();
  //for (int i = 0; i < dimension; i += 4) { 
  //  tower.pixels[i] = color(0, 0, 0); 
  //} 
  //tower.updatePixels();
  
  //frameRate(1000); //If you want to speeds things up!
}

int dim;
int dimension;

void draw() {
  background(0);
  
  tower.loadPixels();
  //for (int i = 0; i < dim; i++) { 
    tower.pixels[dim] = color(255); 
  //} 
  tower.updatePixels();
  
  dim+=1; // Code will crash once it exceeds dimension-1 ! Add code to correct.
  
  image(tower, mouseX, mouseY);
}

I can only point you in the right direction… you must take the steps to get there.

Keep working at it.

:)

So modifyImage() is moved from setup() to draw(); I like it.

Perhaps I should tell you exactly what I want to do. This will explain both the kind of movement and the editing. Saying I have an image that is 1410 x 2072 px. I want to see a quarter of the image (705 x 1036) at any one time. I can’t load an image that 1410 x 2072 px with my monitor and I do not want to resize the image because that will make everything too small to see.

I know how to move the image so that one quarter is visible at a time.

BUT when I am looking at the quarter of the image, I want to do things to it. For example, I might want to recolour a pixel. I have been using:

set(mouseX, mouseY, black). This is probably the wrong method to use.

When I have done some editing with a quarter of the image, I want to show a different quarter, do some more editing and so on.

Is it possible to do what I want?

1 Like

Could you maybe share your code? Do you essentially want to make a painting app that can zoom into parts of an image and paint pixels, then save that image? Your initial issue was changes not persisting between frames, that has been solved, is the new issue that you want to be able to paint pixels with the mouse pointer?

Here it is, but of course it is wrong. The issue has not changed. I am not sure the changes persisting has been solved - if I want to change pixels in the images (as below). Also, I do not want to zoom in if zooming means changing scale. It’s more like tracking across the image horizontally and vertically…
Thank you for your patience. It feels like we are one line of code away from a solution.

PImage img;
int myx;
int myy;
color black = color(0);
color white = color(255);

// quadrant adjusting
int shiftX = 0;
int shiftY = 0;

String imagename = "quadrants.tif"; // an image that is 800 x 1200. Twice the screen width. Twice the screen height.

void setup() {
  size(400, 600); // size of one quarter of image
  img = loadImage(imagename);
  image(img, 0, 0);
}

void draw() {
  myx = mouseX + shiftX;
  myy = mouseY + shiftY;
}





void keyPressed() {

  // mark the image
  if (key == 'x') {


    fill(white);
    noStroke();
    ellipse(mouseX, mouseY, 21, 21);

    // colour centre pixel black
    set(mouseX, mouseY, black);
  }


  // change quarter showing
  if (key == '0') {  // red
    image(img, 0, 0);
    shiftX = 0;
    shiftY = 0;
  } else if (key == '1') { // yellow
    image(img, -400, 0);
    shiftX = -400;
    shiftY = 0;
  } else if (key == '2') { // green
    image(img, 0, -600);
    shiftX = 0;
    shiftY = 0;
  } else if (key == '3') { // blue
    image(img, -400, -600);
    shiftX = 0;
    shiftY = 0;
  }
}

Ahh I see now. So two options are either to 1. draw the ellipses (or whatever else you want to draw onto the image) by pixels rather than using the ellipse function, or 2. to keep track of what you’ve drawn on each panel and then just redraw them when the user comes back to the panel.

OK! So, if we go with your first option.

So the one remaining problem (for me) is how?
Let’s have a simple example. Do I loop through all the pixels until I get to the location of the mouse?

in setup()

picture = loadImage("quadrants.tif");
int dimension = picture.width * picture.height;

in draw()

picture.loadPixels();
// Loop through every pixel column
for (int x = 0; x < width; x++) {
  // Loop through every pixel row
  for (int y = 0; y < height; y++) {

    int loc = x + y * width;
    if ((x == mouseX) && (y == mouseY)) {
      picture.pixels[loc] = color(black);
    } 
  }
}
  picture.updatePixels();

This will draw a circle and update the image itself to have those pixels, then you’ll just have to change it a bit so the mouse positions are relative to the full image.

void drawCircleAroundMouse(color circleColor) {
  int radius = 20;
  for (int i = -radius; i <= radius; i++) {
    for (int j = -radius; j <= radius; j++) {
      if (dist(i, j, 0, 0) <= radius) {
        int px = mouseX + i;
        int py = mouseY + j;
        img.set(px, py, circleColor);
      }
    }
  }
  updatePixels();
}

I have put your code (thank you!) into a complete but minimal sketch (below). I have simplified matters by removing any image movement. So, pressing the ‘x’ key should draw a circle around the mouse location.

It doesn’t. What have I done wrong???

PImage img;
color black = color(0);

String imagename = "quadrant1.tif";

void setup() {
  size(400, 600);
  img = loadImage(imagename);
  image(img, 0, 0);
}

void draw() {
}

void keyPressed() {
  if (key == 'x') {
    drawCircleAroundMouse();
  }
}

void drawCircleAroundMouse() {
  int radius = 20;
  for (int i = -radius; i <= radius; i++) {
    for (int j = -radius; j <= radius; j++) {
      if (dist(i, j, 0, 0) <= radius) {
        int px = mouseX + i;
        int py = mouseY + j;
        img.set(px, py, black);
      }
    }
  }
  updatePixels();
}

OK!! Now it is working (below). One small glitch. I have to press the key (x) twice for the black circle to draw. Any ideas, or is this a new topic???

Thanks.

PImage img;
color black = color(0);

String imagename = "quadrant1.tif";

void setup() {
  size(400, 600);
  img = loadImage(imagename);
  image(img, 0, 0);
}

void draw() {
}

void keyReleased() {
  if (key == 'x') {
    image(img, 0, 0);
    drawCircleAroundMouse();
  }
}

void drawCircleAroundMouse() {
  int radius = 20;
  for (int i = -radius; i <= radius; i++) {
    for (int j = -radius; j <= radius; j++) {
      if (dist(i, j, 0, 0) <= radius) {
        int px = mouseX + i;
        int py = mouseY + j;
        img.set(px, py, black);
      }
    }
  }
  img.updatePixels();
}

Putting drawCircleAroundMouse() above image() will fix this problem. The underlying issue is that once something has been drawn to the screen you can’t update it without redrawing it or updating the canvas itself. You can think of Processing of having stamped the version of the image at that time down onto the canvas.

Stepping through your original code, calling image() will draw the unmodified img to the screen, then callling drawCircleAroundMouse() will update the img variable to have the circle in it, meaning the second time you call img() it will draw that version with a single circle instead of two circles.

Calling img.set() will update the img variable, then you just have to make sure that what you have on screen is the most recent version of the img, because modifying the img variable but not drawing it will not show any visual results.

1 Like

Excellent. All understood and appreciated. So here is both movement and editing in one fully functioning sketch:
[The single pixels will be used as seeds for Voronoi cells, and the image will be far more interesting than four coloured quadrants!]

PImage img;

// quadrant adjusting
int shiftX = 0;
int shiftY = 0;

int varX = 0;
int varY = 0;

color black = color(0);
color white = color(255);

String imagename = "quadrants";
String suffix = ".tif";

void setup() {
  size(400, 600);
  String filename = imagename + suffix;
  img = loadImage(filename);
  image(img, 0, 0);
  varX = img.width/2; // a little loss of accuracy
  varY = img.height/2; // if width or height is odd number
}

void draw() {
}

void keyReleased() {
  if (key == 'x') {
    drawCircleAroundMouse();
  }

  if (key == 'p') {
    img.save("data/"+imagename+"_dotted.tif");
    println(imagename+"_dotted.tif Saved");
  }


  // change quarter showing
  if (key == '0') {  // red
    shiftX = 0;
    shiftY = 0;
  } else if (key == '1') { // yellow
    shiftX = -varX;
    shiftY = 0;
  } else if (key == '2') { // green
    shiftX = 0;
    shiftY = -varY;
  } else if (key == '3') { // blue
    shiftX = -varX;
    shiftY = -varY;
  }
  image(img, shiftX, shiftY);
}

void drawCircleAroundMouse() {
  int radius = 20;
  for (int i = -radius; i <= radius; i++) {
    for (int j = -radius; j <= radius; j++) {
      if (dist(i, j, 0, 0) <= radius) {
        int px = mouseX + i;
        int py = mouseY + j;
        img.set(px - shiftX, py - shiftY, white);
      }
      img.set(mouseX - shiftX, mouseY - shiftY, black);
    }
  }
  img.updatePixels();
}
1 Like

Nicely done! The start of something pretty cool.

When your image is bigger than the screen and it’s dragged to one corner, mouseX, mouseY won’t work directly.

Instead you have to add the offset of the image to the mouse position.

2 Likes