Creating a "looking-glass" (an ellipse which "sees" the image underneath it)

edited October 2016 in Questions about Code

Hello Processing community,

I am a little new with this whole thing, so bear with me:

Basically, for a project of mine, I wish to create a looking glass. During the scenes in this project (its an interactive story of sorts) two gifs will be running simutaneously, one on top of the other. The gifs are run using the GifAnimation library. The idea is that the user can move the looking glass over some area of the gif on top, and, in the area of the looking glass, the user will be able to see the gif underneath; within the area of the looking glass, the gif on top becomes transparent. The two gifs will be fairly similar, however the hidden one will have small, subtle changes that reveal additional information about the story.

What I have so far:

Firstly, the LookingGlass class which is called by draw() is defined like so. lg is defined as a global variable, LookingGlass lg in the main file.

`class LookingGlass {
  float radius = 50;

  // This is a method which determines which pixels on the screen are inside of the looking glass, and which ones are not.
  void makeTransparent() {
    verticies[0] = new Vect2(mouseX-(lg.radius)/2, mouseY);
    verticies[1] = new Vect2(mouseX-(lg.radius)/3, mouseY-(lg.radius)/3);
    verticies[2] = new Vect2(mouseX, mouseY-(lg.radius)/2);
    verticies[3] = new Vect2(mouseX+(lg.radius)/3, mouseY-(lg.radius)/3);
    verticies[4] = new Vect2(mouseX+(lg.radius)/2, mouseY);
    verticies[5] = new Vect2(mouseX+(lg.radius)/3, mouseY+(lg.radius)/3);
    verticies[6] = new Vect2(mouseX, mouseY+(lg.radius)/2);
    verticies[7] = new Vect2(mouseX-(lg.radius)/3, mouseY+(lg.radius)/3);

    // I have put various things here which have all failed, as described below

    }
  }

  // Displays the looking glass, and has it gain transparency
  void display() {
    fill(255, 20);
    ellipse(mouseX, mouseY, radius, radius);
  }
}`

Originally, I though that you could use pixels[] to make each the pixels with the above listed verticies (which roughly encompass the same area as the circle) transparent, by using the point2line library's method insidePolygon, which takes a list of Vect2 (the polygon's verticies) and a Vect2 (the point which you wish to test whether it is inside the bounds defined by those verticies). Nevertheless, this is TERRIBLY inefficient - my window size is 690x388, so each time the looking glass moves, it has to recalcuate which of the 267720 pixels on the screen are inside the looking glass and which ones are not.

I don't know if I could use PGraphics to somehow resolve this issue - I don't know enough about Processing in general.

Regardless, any help would be appreciated.

EDIT: Formatting

Answers

  • edited October 2016

    It seems like you want to use Pimage.mask() and implement a mouse-controlled mask.

    Leave your image layers A and B alone and only reapply a precalculated mask image layer each draw. You can simply draw the mask -- for example, draw an ellipse, or a filled polygon -- then apply the mask to image A or image B, depending on if you want a cutout or a reverse. To be clear: render the mask layer pixels once, in setup -- then only apply it, never recalculate it.

    Note that if performance is still an issue, masking a small, cropped "under" image that is actually drawn on top of your base layer might be a more performance efficient way of achieving your effect -- but what will work for you in the end depends on your images and how you are handling transparency and compositing effects.

  • edited October 2016

    Thank you jeremydouglass, this was exactly what I was looking for.

    If I could just annoy you with a further question, specifically applying (showing) the mask in a given area. Given the code:

        PImage foregroundImage, backgroundImage, maskImage;
        PGraphics pg;
        LookingGlass lg;
        float radius = 100;
    
        void setup() {
          size(1920, 1200, P2D);
          foregroundImage = loadImage("image1.jpg");
          backgroundImage = loadImage("image2.jpg");
          maskImage       = loadImage("mask.png");
          pg = createGraphics(1920, 1200, P2D);
          pg.beginDraw();
          pg.ellipse(mouseX, mouseY, radius, radius);
          pg.endDraw();
          foregroundImage.mask(pg);
          //foregroundImage.mask(maskImage);
          lg = new LookingGlass();
        }
    
        void draw() {
          image(backgroundImage, 0, 0);
          image(foregroundImage, 0, 0);
          lg.display();
        }
    
        class LookingGlass {
          float radius = 100;
    
          // Displays the looking glass, and has it gain transparency
          void display() {   
            fill(255, 20);
            ellipse(mouseX, mouseY, radius, radius);
          }
        }
    

    Where foregroundImage and backgroundImage are just some PImages sized 1920x1200 and maskImage is a 1920x1200 image which is completely transparent.

    I'm not certain where to go from here: if I uncomment foregroundImage.mask(maskImage); the foregroundImage effecively disappears, however I only want a circular section of the foreground image to disappear when the pg graphics object (the ellipse) moves over it.

  • edited November 2016

    @VD_D you are very close.

    First study this example from PhiLo in 2009, which is a working example of the effect you want. It re-renders the graphicalMask mask every draw loop before using it to create the maskImage for display.

    https://processing.org/discourse/beta/num_1231933751.html

    Then, consider this working example, which is based on your work and his:

    /**
     *  Mask to Reveal Hidden Image
     *  2016-10-19 Jeremy Douglass Processing 3.2.1
     *  based on a sketch by PhiLo -- https:// processing.org/discourse/beta/num_1231933751.html
     *  and a sketch by VD_D -- https:// forum.processing.org/two/discussion/18575/creating-a-looking-glass-an-ellipse-which-sees-the-image-underneath-it#latest
     **/
    
    PImage imgForeground;
    PImage imgHidden;
    PGraphics pgMask;
    PImage imgMask;
    int iw, ih;
    int dw, dh;
    float maskSize;
    
    void setup()
    {
      size(300, 300);
      maskSize = 100;
      imgHidden = loadImage("http:// www.gardensnob.com/pictures/strawberries1-300x300.jpg");
      imgForeground = loadImage("http:// s3files.core77.com/blog/images/At-symbol-4.sm_1-300x300.jpg");
      iw = imgHidden.width;
      ih = imgHidden.height;
      dw = width - iw;
      dh = height - ih;
      pgMask = createGraphics(iw, ih);
    }
    
    void draw()
    {
      background(200);
      image(imgForeground,0,0);
      pgMask.beginDraw();
        // Erase graphics
        pgMask.background(0);
        // Draw the mask
        int x = mouseX - dw/2;
        int y = mouseY - dh/2;
        pgMask.noStroke();
        //// mostly transparent circle (250) with fuzzy edge (0-250)
        for(int i=0;i<25;i++){
          pgMask.fill(0+i*10);
          pgMask.ellipse(x, y, maskSize-i*2, maskSize-i*2);
        }
        //// or just a simple circle
        // pgMask.fill(255);
        // pgMask.ellipse(x, y, maskSize, maskSize);
      pgMask.endDraw();
      //// Copy the original image (kept as reference)
      imgMask = imgHidden.get();
      //// Apply mask and display result
      imgMask.mask(pgMask);
      image(imgMask, dw/2, dh/2);
      //// Draw a magnifying frame
      stroke(96,48,32);
      strokeWeight(maskSize/10);
      noFill();
      ellipse(mouseX,mouseY,maskSize,maskSize);
      //// ...and handle
      strokeWeight(maskSize/5);
      line(mouseX+(maskSize/2/sqrt(2)),mouseY+(maskSize/2/sqrt(2)),mouseX+maskSize*3/4,mouseY+maskSize*3/4);
    }
    

    Now, how can you create a LookingGlass class and call it based on this working example?

    • For example, you might want to put imgHidden and maskSize in the class constructor, so that when you create a new lookingGlass you determine how big it is and what it will show.
    • Some things, like pgMask and imgMask, don't need to be global variables once you have a class -- they can be used inside the LookingGlass class to build the image.
    • LookingGlass.display() could either draw directly... or could return a PImage -- to be called in your draw loop with image(lg.display(),0,0);

    etc.

Sign In or Register to comment.