# How to rasterize a line when the background color is not always white?

I've to rasterize a line taking into account not only the background color of the screen, which is white, but also the color of other objects in the scene. In my case, I've just another object, a sphere, which is actually visualized using a ray tracing technique.

So far the code that I have seems only to consider the white background color of the screen and not also the color of the sphere.

So here's the code:

// ray tracing of the sphere
// here we initialize the z-buffer's elements to -1.0 zBuffer that you find below
...

//
// rasterize a line
//

// line from p1 to p2 in LOCAL coordinates
Point3d p1 (-1.0, 0.0, 0.0);
Point3d p2 ( 1.0, 0.0, 0.0);

// transformations...
Matrix4d S  = Matrix4d::scaling (5.0, 1.0, 1.0);
Matrix4d R1 = Matrix4d::rotation (0.5,'z');
Matrix4d R2 = Matrix4d::rotation (tau,'x');
Matrix4d T  = Matrix4d::translation (0.0, 0.0, 8.0);

Matrix4d M  = T*R2*R1*S;

p1 = M*p1;
p2 = M*p2;

// screen coordinates
double x1 = ( X + p1.x()/p1.z() ) / s - 0.5;
double y1 = ( Y - p1.y()/p1.z() ) / s - 0.5;
double x2 = ( X + p2.x()/p2.z() ) / s - 0.5;
double y2 = ( Y - p2.y()/p2.z() ) / s - 0.5;

//
// line rasterization algorithm with anti-aliasing and z-test
// (for lines with slope between -1 and 1)
//
int x  = round(x1);
int y  = round(y1);

double I0 = 1.0;
double m = (y2-y1) / (x2-x1);

bool up = true;
if (y2 < y1) {
m = -m;
I0 = 0.0;
up = false;
}

int nextY;
double z;
for ( ; x<=x2; x++) {
if (up)
nextY = y+1;
else
nextY = y-1;

// barycentric coordinates of the current point: (1-lambda, lambda),
// i.e.: x = (1-lambda) x1 + lambda x2
double lambda = (x-x1) / (x2-x1);

// perspectively correct linear interpolation of z-values
z = p1.z()*p2.z() / ( (1.0-lambda)*p2.z() + lambda*p1.z() );

if ( (zBuffer[x+y*w] < 0) || z < zBuffer[x+y*w] ) {
int I = (int) 255*I0;
image->setPixel( x, y,     qRgb( 255-I, 255, 255-I ) );
image->setPixel( x, nextY, qRgb(     I, 255,     I ) );
}

if (m > I0) {
y  = nextY;
I0 += 1.0;
}
I0 -= m;
}

// repaint canvas
repaint();


Now the output is not as expected, because one of the pixels of the lines on the sphere is coloured white. Of course we notice this immediately! The idea is to make a smoother line that considers not only the colour of the background but also the colour of the sphere.

I thought I need to interpolate colours, but I'm not sure which ones and how to do it. I know that the basic idea of interpolation is to find an intermediate colour between two (or more) colours ($C_1, C_2$) using a formula as follows:

$$(1 - \alpha) C_1 + \alpha * C_2$$

Now the problem is that I don't know what's exactly $\alpha$ and which colours would be $C_1$ and $C_2$. I thought $\alpha$ could be the lambda variable in the code above, but this is more than a guess than a conclusion after a logical reasoning.

Moreover, I guessed that the colours $C_1$ and $C_2$ are respectively the color of the pixel before the rasterization of the line (i.e. in the ray tracing part), which can also be the color of the sphere, if that pixel is covered by the sphere, or the background color, i.e. white in my case.

Any help is appreciated.

To give you an idea, this is the wrong output:

and this should be the correct one:

Note (if you zoom in) that the line in the first picture is not that smooth over the sphere, because one of the pixels (at least) is colored for the background color white.

Edit

After trying to interpolate green and the current background color (as also Nathan Reed suggested), I obtained:

which is correct in the sense that I think that the interpolation is being done correctly, but at this point I think interpolation is not what we need to do to obtain a smooth line.

To obtain this I just added a few lines when setting the pixels of the line:

int I = (int)255 * I0;

Color3d c = pixels_colors[x + y * w];
// pixels_buffer contains the color at pixel x + y * w
// this color was saved while ray tracing the sphere

c.clamped();

int red = c.r() * 255;
int green = c.g() * 255;
int blue = c.b() * 255;

image->setPixel(x, y, qRgb(red - I, green, blue - I));
image->setPixel(x, y, qRgb(I, green, I));


Edit 2

After the corrections suggested by Nathan Reed, I obtained:

The colors of the pixels of the line over the sphere now really seem to be the product of the interpolation between the color green and the color of the sphere. Here's the code:

int I = (int)255 * I0;

Color3d background_color = pixels_colors[x + y * w];
Color3d green(0, 1.0, 0);

Color3d color_y = (1 - I0) * background_color + I0 * green;
Color3d color_next_y = I0 * background_color + (1 - I0) * green;

image->setPixel(x, nextY, qRgb(color_y.r() * 255, color_y.g() * 255, color_y.b() * 255));
image->setPixel(x, y, qRgb(color_next_y.r() * 255, color_next_y.g() * 255, color_next_y.b() * 255));


If this is now correct, the picture still doesn't look as it should. I think we may need to color more pixels?!

You're actually already doing color interpolation in the code you posted, in these expressions:

qRgb( 255-I, 255, 255-I )
qRgb(     I, 255,     I )


The first one is interpolating from white to green by I, and the second one from green to white by I. In both cases I is acting as an integer form of the variable $\alpha$ from the equation. I note that you have already a fractional variable I0 there, which is exactly what's needed for interpolation.

So, if I rewrite your qRgb expressions a little bit, I can make them look closer to the form of the interpolation equation:

(1.0 - I0) * qRgb(255, 255, 255) + I0 * qRgb(0, 255, 0)
I0 * qRgb(255, 255, 255) + (1.0 - I0) * qRgb(0, 255, 0)


This hopefully makes it clearer that $C_1$ is the color we're interpolating from, white, and $C_2$ is the color we're interpolating to, green. In the first line $\alpha$ is I0 and in the second, $\alpha$ is 1.0 - I0. Note that interpolating from white to green by 1.0 - I0 is equivalent to interpolating from green to white by I0.

So, to make this work against an arbitrary colored background, you just have to change white to the actual background color of the pixel you're rasterizing to.

Technically, gamma correction should also be applied when interpolating, but that's a relatively minor visual change that you can safely ignore for awhile.