0

I have a 2D scene in Monogame with some primitives and sprites (i.e. in PrimitiveBatches and SpriteBatches) and I would like to create a magnifying glass effect with a circular lens showing a zoomed view of the content under it. How do I do that?

Thanks.

Tamori
  • 97
  • 9

1 Answers1

1

I do not use your environment but I always did this effect with pixel displacement. If you got pixel access to rendered scene (ideally while still in a back-buffer so it does not flicker) then just move the pixels inside your lens to the outward positions. Either use constant displacement or even better is when you move more (bigger zoom) in the middle and less near the edges.

Typical implementation looks like this:

  1. copy lens area to some temp buffer
  2. loop (x,y) through lens area
  3. compute actual radius r of processed pixel from lens center (x0,y0) ignore pixels outside lens area (r>R)
  4. compute actual zoom m of processed pixel

    I like to use cos for this like this:

    m=1.0+(1.5*cos(0.5*M_PI*double(r)/double(r0))); // M_PI=3.1415...
    

    you can play with the 1.0,1.5 constants. They determine minimal (1.0) and maximal (1.0+1.5) zoom. Also this is for cos taking angle in [rad] so if yours need [deg] instead change the 0.5*M_PI with 90.0

  5. copy pixel from temp to backbuffer or screen

    backbuffer(x,y)=temp(x0+(x-x0)/m,y0+(y-y0)/m)
    

Here C++/VCL example:

void TMain::draw()
    {
    // clear bmp (if image not covering whole area)
    bmp->Canvas->Brush->Color=clBlack;
    bmp->Canvas->FillRect(TRect(0,0,xs,ys));
    // copy background image
    bmp->Canvas->Draw(0,0,jpg); // DWORD pxy[ys][xs] is bmp direct pixel access, (xs,ys) is bmp size
    // here comes the important stuff:
    int x0=mx,y0=my;            // position = mouse
    const int r0=50;            // radius
    DWORD tmp[2*r0+3][2*r0+3];  // temp buffer
    double m;
    int r,x,y,xx,yy,xx0,xx1,yy0,yy1;
    // zoom area bounding box
    xx0=x0-r0; if (xx0<  0) xx0=0;
    xx1=x0+r0; if (xx1>=xs) xx1=xs-1;
    yy0=y0-r0; if (yy0<  0) yy0=0;
    yy1=y0+r0; if (yy1>=ys) yy1=ys-1;
    // copy bmp to tmp
    for (y=yy0;y<=yy1;y++)
     for (x=xx0;x<=xx1;x++)
      tmp[y-yy0][x-xx0]=pyx[y][x];
    // render zoomed area
    for (y=yy0;y<=yy1;y++)
     for (x=xx0;x<=xx1;x++)
        {
        // compute radius
        xx=x-x0;
        yy=y-y0;
        r=sqrt((xx*xx)+(yy*yy));
        if (r>r0) continue;
        if (r==r0) { pyx[y][x]=clWhite; continue; }
        // compute zoom: 2.5 on center, 1.0 at eges
        m=1.0+(1.5*cos(0.5*M_PI*double(r)/double(r0))); // M_PI=3.1415...
        // compute displacement
        xx=double(double(xx)/m)+x0;
        yy=double(double(yy)/m)+y0;
        // copy
        if ((xx>=xx0)&&(yy>=yy0)&&(xx<=xx1)&&(yy<=yy1))
         pyx[y][x]=tmp[yy-yy0][xx-xx0];
        }
    // just refresh screen with backbuffer
    Canvas->Draw(0,0,bmp);
    }

And here animated GIF preview (quality and fps is lowered by GIF encoding):

preview

If you need help with understanding the gfx access in my code see:

Community
  • 1
  • 1
Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Thanks for that detailed suggestion. I'm not sure how to access the pixels in my Monogame pipeline, though. And I certainly would want to avoid just zooming rasterised vector data as that would give a pixellated scaled view. – Tamori Oct 20 '16 at 15:36
  • In Java2D I would create a Graphics object from a new image, apply a scale transform corresponding to the zoom factor, draw my content in it with a clipping region matching the lens shape and then draw the image in the paint method of my canvas. I imagine the strategy is similar in XNA, but I don't know the exact API elements I can use to do that. I searched a bit and it seems I might have to define a RenderTarget2D, draw my stuff in there with my rendering settings and then somehow draw it in the back buffer. Not sure how I would apply a scale transform and circular clipping to it, though. – Tamori Oct 20 '16 at 15:41