Tamori
Tamori

Reputation: 97

Drawing a circular magnifying lens showing scaled underlying content in XNA/Monogame (in 2D)

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.

Upvotes: 0

Views: 160

Answers (1)

Spektre
Spektre

Reputation: 51855

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:

Upvotes: 1

Related Questions