0

I am trying to create an object and for it to have a shape (an ellipse), an image, and a function that somehow fills the shape with a pre-loaded image.

I already found this post, but I can't get it to work if I try to make it neat and fit it all into an object.

To be clear, this is what it would look like:

let image;
let hypotheticalObject
function preload()
{
   image = loadImage('Assets/randomimage.jpeg');
}


function setup()
{
    hypotheticalObject = {
        objectImage: image,
        graphicsBuffer: createGraphics(100, 100),
        xPos: width / 2,
        yPos: height / 2,
        size: 50,
        colour: color(255, 255, 255),

        shape: function()
        {
            fill(this.colour),
            ellipse(this.xPos, this.yPos, this.size);
        },
        
        mask: function()
        {
            this.graphicsBuffer.shape();
        },

        render: function()
        {
            something something
        }
        
}

function draw()
{
    hypotheticalObject.render();
}

That's kind of how far I can get, as I can't figure out how to proceed.

Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
zerohero
  • 33
  • 4

1 Answers1

0

There are a couple of issues with your code, namely this:

shape: function() {
  // ...
},

mask: function() {
  this.graphicsBuffer.shape();
},

You declare shape() on hypotheticalObject but then you try to call it on graphicsBuffer? This isn't how javascript works. You could do something like this:

hypotheticalObject.graphicsBuffer.shape = function() {
  // When graphicsBuffer.shape() is called, the this keyword will be bound to graphicsBuffer
  // Unfortunately we need a to access members from hypotheticalObject as well, so that is inconvenient:
  this.fill(hypotheticalObject.colour),
  this.ellipse(hypotheticalObject.xPos, hypotheticalObject.yPos, hypotheticalObject.size);
}

However I don't think that is the most idiomatic approach either. Here's a working solution that tries to stay close to your code:

let img;
let hypotheticalObject;

function preload() {
  img = loadImage('https://www.paulwheeler.us/files/windows-95-desktop-background.jpg');
}

function setup() {
  createCanvas(windowWidth, windowHeight);
  
  hypotheticalObject = {
    objectImage: img,
    // The graphics you're using for the mask need to be the size of the image
    graphicsBuffer: createGraphics(img.width, img.height),
    xPos: width / 2,
    yPos: height / 2,
    size: 50,
    
    init: function() {
      this.maskedImage = createImage(this.objectImage.width, this.objectImage.height);
    },

    updateShape: function() {
      this.graphicsBuffer.clear();
      // The fill color is irrelevant so long as it is opaque
      this.graphicsBuffer.fill(0);
      // These drawing instructions need to happen on the p5.Graphics object
      this.graphicsBuffer.ellipse(this.xPos, this.yPos, this.size);
    },

    render: function() {
      this.updateShape();
      // When you call mask on an image it changes the image so we need to make a copy
      this.maskedImage.copy(
        this.objectImage,
        0, 0, this.objectImage.width, this.objectImage.height,
        0, 0, this.objectImage.width, this.objectImage.height
      );
      this.maskedImage.mask(this.graphicsBuffer);
      // Always draw the image at 0, 0 since the masked portion is already offset
      image(this.maskedImage, 0, 0);
    }
  }

  hypotheticalObject.init();
}

let xv = 2;
let yv = 2;

function draw() {
  background(255);
  
  hypotheticalObject.xPos += xv;
  hypotheticalObject.yPos += yv;
  
  if (hypotheticalObject.xPos > width) {
    hypotheticalObject.xPos = width;
    xv *= -1;
  } else if (hypotheticalObject.xPos < 0) {
    hypotheticalObject.xPos = 0;
    xv *= -1;
  }
  if (hypotheticalObject.yPos > height) {
    hypotheticalObject.yPos = height;
    yv *= -1;
  } else if (hypotheticalObject.yPos < 0) {
    hypotheticalObject.yPos = 0;
    yv *= -1;
  }
  
  hypotheticalObject.render();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

Update: Animated Gifs

If you want this to work with animated gifs, you have to deal with an idiosyncrasy of p5.js: the fact that it updates gif images only when they are rendered with either the image() function or the texture() function. And that rendering code depends on the drawing context having timing information, and therefor does not work with off screen p5.Graphics buffers. Here's an example:

let img;
let hypotheticalObject;

function preload() {
  img = loadImage('https://www.paulwheeler.us/files/Clouds.gif');
}

function setup() {
  createCanvas(img.width, img.height);

  hypotheticalObject = {
    objectImage: img,
    // The graphics you're using for the mask need to be the size of the image
    graphicsBuffer: createGraphics(img.width, img.height),
    xPos: width / 2,
    yPos: height / 2,
    size: 100,

    init: function() {
      this.maskedImage = createImage(this.objectImage.width, this.objectImage.height);
    },

    updateShape: function() {
      this.graphicsBuffer.clear();
      // The fill color is irrelevant so long as it is opaque
      this.graphicsBuffer.fill(0);
      // These drawing instructions need to happen on the p5.Graphics object
      this.graphicsBuffer.ellipse(this.xPos, this.yPos, this.size);
    },

    render: function() {
      this.updateShape();
      // When you call mask on an image it changes the image so we need to make a copy
      this.maskedImage.copy(
        this.objectImage,
        0, 0, this.objectImage.width, this.objectImage.height,
        0, 0, this.objectImage.width, this.objectImage.height
      );
      this.maskedImage.mask(this.graphicsBuffer);
      // Always draw the image at 0, 0 since the masked portion is already offset
      image(this.maskedImage, 0, 0);
    }
  }

  hypotheticalObject.init();
}

let xv = 2;
let yv = 2;

function draw() {
  // In order to get a gif to animate it needs to be passed to either the image() function or the texture() function. And unfortunately it must be the global one, not the version on an off-screen p5.Graphics
  // However we don't actually want to display this
  image(img, width, height);
  background(255);

  hypotheticalObject.xPos += xv;
  hypotheticalObject.yPos += yv;

  if (hypotheticalObject.xPos > width) {
    hypotheticalObject.xPos = width;
    xv *= -1;
  } else if (hypotheticalObject.xPos < 0) {
    hypotheticalObject.xPos = 0;
    xv *= -1;
  }
  if (hypotheticalObject.yPos > height) {
    hypotheticalObject.yPos = height;
    yv *= -1;
  } else if (hypotheticalObject.yPos < 0) {
    hypotheticalObject.yPos = 0;
    yv *= -1;
  }

  hypotheticalObject.render();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Paul Wheeler
  • 18,988
  • 3
  • 28
  • 41
  • Thank you, it worked. That said, my image was a GIF, which is not animating anymore with this method. I suspect it's because createImage creates a new JPEG or something based on the original image, in this case a frame of the GIF. I also removed this.graphicsBuffer.clear(); and this.graphicsBuffer.fill(0); because it works the same without them. Thank you once again for your help! – zerohero Jan 02 '22 at 02:22
  • I added an explanation of how to make this work with gifs. – Paul Wheeler Jan 02 '22 at 10:34