Intro
This tutorial is designed to make you start thinking about what effects can be done to images. Minute distortions in a scene can lead to very nice transitions and effects. I present here a very simple, cheap and dirty method of creating a lens effect in a scene. Later I will add other routines that modify how the lens looks, they will have variable refraction amounts, and less special cases. This first one will get your creative juices flowing!
Method 1
We will be using the routine in the circle drawing routine to give us our edges for our lens. We can then scale each line according to the amount of refraction desired. As refraction amounts get greater, we get closer and closer to being forced to deal with special cases, which will give our lens effect more flexibility, but it will be harder to make it any faster. Lets go through the routine function by function.
-
void se::Lens(long xcenter,long ycenter,long radius)
-
{
-
long x=0;
-
long y=radius;
-
long p=(5-radius*4)/4;
-
LensStrips(xcenter,ycenter,x,y);
-
while(x<y)
-
{
-
x++;
-
if(p<0)
-
{
-
p+=2*x+1;
-
}
-
else
-
{
-
y--;
-
p+=2*(x-y)+1;
-
}
-
LensStrips(xcenter,ycenter,x,y);
-
}
-
memset(buffer,0,1000);
-
}
This is our main function which will be called. All 3 functions are in my Special Effects class, and I have se defined as SpecialEffects so I don't kill myself trying to type it out every time. You will notice that this is 98% the same function that we had for drawing circles. We have our function follow the path of the circle, when it tries to go out of bounds we use one of the two functions to correct its course. This method works very well. From the look of this function, we could be drawing a circle, our real tricks are in our lower levels of abstraction. On we go!
-
void se::LensStrips(long cx,long cy,long x,long y)
-
{
-
if(x==0)
-
{
-
SHL(cx-y,cx+y,cy);
-
}
-
else if(x==y)
-
{
-
SHL(cx-x,cx+x,cy+y);
-
SHL(cx-x,cx+x,cy-y);
-
}
-
else if(x<y)
-
{
-
SHL(cx-x,cx+x,cy+y);
-
SHL(cx-x,cx+x,cy-y);
-
SHL(cx-y,cx+y,cy+x);
-
SHL(cx-y,cx+y,cy-x);
-
}
-
}

This function looks remarkably like our function that plotted the pixels in our circle routine. The only exception is that we are now calling our SHL function. We pass the two x extents, left and right, and pass the mutual y. The SHL function takes care of the rest!
-
void se::SHL(long x1,long x2,long y)
-
{
-
if(ybuffer[y]){return;}
-
ybuffer[y]=1;
-
float scale,xcum=0;
-
char* t;
-
t=(char*)video->video_buffer+y*video->Screen_Width+x1;
-
long count,count2,xcount;
-
-
xcount = x2-x1;
-
scale = ((float)xcount+60.0)/((float)xcount);
-
unsigned char* temp= new uc[xcount+60];
-
memmove(temp,t-30,xcount+60);
-
for(count=0,xcum=0.0;count<xcount;count++,xcum+=scale)
-
{
-
video->quickpixel(x1+count,y,temp[(long)xcum]);
-
}
-
delete temp;
-
}
Here's where the guts are! We are going to use the outer edges of the circle to be our starting and end points for each horizontal line. Each line will be scaled to smaller horizontal line. In essence we are taking the circle apart by horizontal slivers and scaling each one
individually. The problem we first run into is that the circle routine was created to draw a complete circle, and y values are duplicated, more at the top and bottom. In the image on the right, we see a white arc which is the path our main routine follows. All other points are plotted by using symmetry. Notice all the repeated y values, we actually only need the ones in blue. Not only are we wasting cycles traversing through points we dont need, if we try to scale multiple lines with the same y, we will get disortions that look horrible. To overcome this, we can make a simple y buffer. I created a char array with a size of the Y resolution of the video mode. Every cycle we test to see if that y line has been set, if so we return, nothing more to be done otherwise set a 1 in the ybuffer to flag we did it and move on. We set t equal to the location in memory where we will be moving memory to. This is the x1 location that is passed to us. We then set xcount which is actually how long our strip is. We then calculate a stepping rate based on how much refraction we want to achieve. We then dynamically allocate a char array to hold the original horizontal line. If we didn't we would be reading and writing in the same memory and we wouldn't get the correct results, they would be distorted. We then go into a for loop that will traverse through our saved horizontal strip by increments that are scale in size.
As it stands the code calculates a total of 60 extra pixels meaning that we read in 30 pixels to the left and right of the target strip and scale them down do fit inside. If we put xcount+60 on right side we would be scaling upwards so it would act like a magnifying glass. If we use it to scale down, meaning use refraction, we have to be careful. With this method we read in t-30 meaning we move back 30 pixels on the screen and begin reading the pixels in. If we are within 30 pixels of the left or right edge we will get a screen wrap around apearing in our len s. This may be ok for you, but I'd rather have it show up black. Also, with this method our lens must be fully on the screen at all times!! If it goes offscreen somewhere (top or bottom) we could get memory errors. Even if we make certain that our lens is on screen, if we have our refraction too high, it could try to access memory outside Vram, and that could cause errors. These sound like pretty stiff conditions huh!? As you can see by the screen shot, it will work, but you have to be very careful how you use it. Future versions will have less chances of crashing. You may not be able to see it very well from the picture, but here are 5 lens effects at work, when animated they look pretty good! There you have it, so far one method of a lens effect, more to come for sure! In all, it is probably one of the easiest methods of just getting it to work. If we are going to use it in any serious application, we really need to redesign and optimize it. Even with my PII 350 it runs at 65 fps in 640x480 mode with a LFB and 5 lense effects. I'm determined to triple that! Another thing to mention is that this routine modifies what is on the screen, you have to either blit your image every cycle or save the portion you wrote over and blit it back after you're done so it doesn't mess up the display.