How to move the image inside rectangle in p5js?

Viewed 102

I'm currently doing a project regarding p5.js and I need to do the following:

Input: given image and rectangle;

Algorithm: move the image inside the rectangle. By saying moving, I mean when the mouse is clicked, move the whole image to match the size of the rectangle by showing only portion of the image (size of the rectangle).

Output: dragging image inside the rectangle.

Update: When I import an image with React input form, it does not change the current image in P5 even I call the p5.redraw() function.

My current code is moving the image, but I need an image to be inside the rectangle and show only part of the image that is this size of the rectangle. The code is written in React:

  const [selectedFile, setSelectedFile] = useState();
  let backgroundImage;
  let dragging = false; // Is the object being dragged?
  let rollover = false; // Is the mouse over the ellipse?

  let x, y, w, h; // Location and size
  let offsetX, offsetY; // Mouseclick offset

  const setup = (p5, parentRef) => {
    p5.createCanvas(1000, 500).parent(parentRef);
    // Starting location
    x = 350;
    y = 50;

    const url =
      "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/640px-Image_created_with_a_mobile_phone.png";
    backgroundImage = p5.loadImage(url);
    // Dimensions
    w = 700;
    h = 700;
  };

  const draw = (p5) => {
    p5.background(233);
    if (
      p5.mouseX > x &&
      p5.mouseX < x + w &&
      p5.mouseY > y &&
      p5.mouseY < y + h
    ) {
      rollover = true;
    } else {
      rollover = false;
    }

    // Adjust location if being dragged
    if (dragging) {
      x = p5.mouseX + offsetX;
      y = p5.mouseY + offsetY;
    }

    if (selectedFile != null) {
      const url = URL.createObjectURL(selectedFile);
      backgroundImage = p5.loadImage(url);
      // p5.image(backgroundImage, x, y, w, h);
      // p5.redraw();
      // backgroundImage = p5.loadImage(url, () => {
      //   p5.image(backgroundImage, x, y, w, h);
      //   p5.redraw();
      // });
    } else {
    }
    p5.image(backgroundImage, x, y);
    drawMaskOverlay(p5);
  };

  const drawMaskOverlay = (p5) => {
    p5.fill(255);
    p5.noStroke();
    p5.beginShape();
    // CW
    p5.vertex(0, 0);
    p5.vertex(p5.width, 0);
    p5.vertex(p5.width, p5.height);
    p5.vertex(0, p5.height);
    // cutout contour CCW
    p5.beginContour();
    p5.vertex(400, 100);
    p5.vertex(400, 400);
    p5.vertex(600, 400);
    p5.vertex(600, 100);
    p5.endContour();
    p5.endShape();
  };

  const mousePressed = (p5) => {
    if (
      p5.mouseX > x &&
      p5.mouseX < x + w &&
      p5.mouseY > y &&
      p5.mouseY < y + h
    ) {
      dragging = true;

      offsetX = x - p5.mouseX;
      offsetY = y - p5.mouseY;
    }
  };

  const mouseReleased = (p5) => {
    // Quit dragging
    dragging = false;
  };

to display it, I use the following code:

  return (
    <div className="App">
      <div>
        <h1>Select an image</h1>
        <input
          type="file"
          name="file"
          id="file"
          // onChange={(e) => setSelectedFile(e.target.files[0])}
          onChange={(e) => setSelectedFile(e.target.files[0])}
        />
        <Sketch
          // preload={preload}
          setup={setup}
          draw={draw}
          mouseReleased={mouseReleased}
          mousePressed={mousePressed}
        />
      </div>
    </div>
  );

I use react-p5 library.

You can see and test the code in StackBlitz.

1 Answers

One quick workaround is to simply overlay a cutout shape using beginContour() / endContour(), thus only showing a portion of the image:

let backgroundImage;
let dragging = false; 
let rollover = false; 

let x, y, w, h; // Location and size
let offsetX, offsetY; 

preload = () => {
  const url = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/640px-Image_created_with_a_mobile_phone.png";
  backgroundImage = loadImage(url);
};

setup = () => {
  createCanvas(1000, 500);
  // Starting location
  x = 100;
  y = 100;

  // Dimensions
  w = 300;
  h = 400;
}

draw = () => {
  background(233);
  if (
    mouseX > x &&
    mouseX < x + w &&
    mouseY > y &&
    mouseY < y + h
  ) {
    rollover = true;
  } else {
    rollover = false;
  }
  if (dragging) {
    x = mouseX + offsetX;
    y = mouseY + offsetY;
  }

  image(backgroundImage, x, y, w, h);
  // noFill();
  // stroke(0);
  // rect(100, 100, 200, 300);
  drawMaskOverlay();
};

function drawMaskOverlay(){
  fill(0);
  noStroke();
  beginShape();
  // CW
  vertex(0, 0);
  vertex(width, 0);
  vertex(width, height);
  vertex(0, height);
  // cutout contour CCW
  beginContour();
  vertex(100, 100);
  vertex(100, 400);
  vertex(300, 400);
  vertex(300, 100);
  endContour();
  endShape();
  
}

mousePressed = () => {
  if (
    mouseX > x &&
    mouseX < x + w &&
    mouseY > y &&
    mouseY < y + h
  ) {
    dragging = true;

    offsetX = x - mouseX;
    offsetY = y - mouseY;
  }
};

mouseReleased = () => {
  dragging = false;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>

If the project is a school project it's unclear whether the above will pass if it doesn't cover the materials taught. If it's a personal project, the above should be a simple enough workaround.

Other alternatives ways of showing parts of the image could be:

  • working with p5.Image which has a mask() method (potentially in conjuction with p5.Graphics which would make it easy draw into and covert to a p5.Image via get())
  • with with p5.Image and instead of masking, clearing a blank (buffer) image and only copying a portion of backgroundImage (depending on it's dragged location) via copy() which would make it appear like backgroundImage is masked.

Update based on your file select comment here's a tweaked version of the above snippet:

let backgroundImage;
let dragging = false; 
let rollover = false; 

let x, y, w, h; // Location and size
let offsetX, offsetY; 

preload = () => {
  const url = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/640px-Image_created_with_a_mobile_phone.png";
  backgroundImage = loadImage(url);
};

setup = () => {
  createCanvas(1000, 500);
  // Starting location
  x = 100;
  y = 100;

  // Dimensions
  w = 300;
  h = 400;

  createFileInput(handleFile);
}

handleFile = (file) => {
  if (file.type === 'image') {
    backgroundImage = createImg(file.data, '');
    backgroundImage.hide();
  } else {
    backgroundImage = null;
  }
}

draw = () => {
  background(233);
  if (
    mouseX > x &&
    mouseX < x + w &&
    mouseY > y &&
    mouseY < y + h
  ) {
    rollover = true;
  } else {
    rollover = false;
  }
  if (dragging) {
    x = mouseX + offsetX;
    y = mouseY + offsetY;
  }
  // only render the image if it exists
  if(backgroundImage)
    image(backgroundImage, x, y, w, h);
  // noFill();
  // stroke(0);
  // rect(100, 100, 200, 300);
  drawMaskOverlay();
};

function drawMaskOverlay(){
  fill(0);
  noStroke();
  beginShape();
  // CW
  vertex(0, 0);
  vertex(width, 0);
  vertex(width, height);
  vertex(0, height);
  // cutout contour CCW
  beginContour();
  vertex(100, 100);
  vertex(100, 400);
  vertex(300, 400);
  vertex(300, 100);
  endContour();
  endShape();
  
}

mousePressed = () => {
  if (
    mouseX > x &&
    mouseX < x + w &&
    mouseY > y &&
    mouseY < y + h
  ) {
    dragging = true;

    offsetX = x - mouseX;
    offsetY = y - mouseY;
  }
};

mouseReleased = () => {
  dragging = false;
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>

Related