8 July, 2010 | Written by Vincent Comments Off

3D Bézier curve editor

Timo Suoranta created a 3D Bézier curve editor. As of this writing, the program runs on Windows and requires OpenGL version 3 or later (shaders are involved). Here’s a screenshot:

Bezier curve editor
[click for larger image]

It looks awesome. What, no? Then you have to understand picking. In 2D, any point you click on the screen is exact. The point you click on is the point selected.

In 3D, it’s different. There are an infinite number of planes behind that virtual screen you picked on. Think of looking up at the clouds in the sky. You know the water droplets are scattered sparsely and densely in the sky. You know they are in a 3D space. But you, looking up at the sky, only see one plane, the 2D plane that has the water droplets rendered onto.

In this case, it’s simpler. We are only concerned with the points on the Bézier curve itself. Timo used the closest point to the clicked point on the screen as the chosen point. Basically you “shoot” a ray from the clicked point into the vanishing point in the far distance (far far far distance, as in infinity far). When your ray hits the Bézier curve, that’s the chosen point. You can find out more about the method by searching on the Internet for “3d picking” or something similar.

So how do you edit a point once it’s chosen? Timo solved it by using 3 cones to represent the X, Y and Z axes. Dragging on the cones move the point along the respective direction of the axis. Notice the 3 cones at the right side.

Bezier curve editor with axes
[click for larger image]

I believe most 3D rendering software use something similar to edit points.

Now notice the small details Timo added:

  • The floor is a checker board to illustrate the notion of space
  • The vertical lines drawn from the points on the curve to the checker board to show the spatial relation
  • If you hover over the big blue spheres, the checker board, or the curve itself, they glow pulse-like

Wait, you haven’t downloaded the program? Here’s the link.

20 April, 2010 | Written by Vincent 4 Comments

Developer Stories

Paparazzi red carpet

I must be getting famous. I was contacted by a social media marketing manager at M80. His company is working with Microsoft to promote Visual Studio 2010. Ok, I think I’ve fulfilled the necessary disclosure requirements. And no, I don’t get anything out of this. And I doubt I’m really that famous, but give me a few seconds to savour my short-lived fame anyway.

Microsoft wants to get feedback, and I believe it doesn’t have to be about Visual Studio 2010 (or Visual Studio at all). They’ve created a YouTube channel called Developer Stories. And they want to know why you are a developer.

If you’re a video kind of person, please go ahead and upload a video of you telling your story. Forget about your biases and opinions about the companies involved. I believe a polymath programmer should be above that. If nothing else, I want to hear your story too.

Since I’ll stutter in front of a video camera, and thank goodness I don’t have one (which is just an excuse, since my bedroom is a lousy backdrop), I’ll have to tell you my story in blog post format.

Once upon a time…

Actually, I stumbled upon programming. Sure I joined the computer club when I was in junior college (about 17 or 18 years old), but I didn’t understand the point of all the arcane Pascal lines of code. When I got into university, I didn’t have the credits (nor background) for the computer science track. So I took up applied mathematics as a major, and computational science as a minor.

In my first semester of my freshman year, I took C programming (which was a requirement for the computational science minor). Variables, assignments, loops, algorithms. It was fun. I also couldn’t understand why some of my classmates had difficulty wrapping their heads around what I perceived as simple ideas. They couldn’t understand some programming concepts, and they had some trouble understanding how to apply and change and break down a problem into programmable, solvable parts. I mean, you calculate something, add it to a temporary variable, go to next iteration, calculate with different input, add to temporary variable, go to next iteration until done. That’s summation. What’s so hard?

It was then that I realised that some people just weren’t made for programming. I’m not saying I’m a genius at it. I may just have a knack for breaking down problems so a programmatic solution is possible. That’s what programming is, not the lines of code and algorithms and what-not. Well, I was good enough at it that I decided to upgrade my computational science minor to a major.

Programming is about solving problems

What’s so special about computational science? And what’s the difference between it and computer science? I’m not sure. Computational science is more about solving (scientific) problems using programming, rather than the programming itself. I’ll leave you to compare that with your understanding of what’s computer science.

And what do I mean by solving problems using programming? I drew Sierpinski triangles. I used Newton-Raphson method to find roots. I solved a gigantic set of 100 equations with a 100 by 100 matrix. And most of the problems were based in science or mathematics.

So my background is about breaking down problems and translating that into programmable parts. I didn’t learn about software development cycles, software management practices and all those complicated stuff. I was trained in the solving problems, not the meta stuff around it. I’m not saying those complicated stuff aren’t useful. Just be aware of what you’re doing.

Here’s a suggestion. Learn about your business processes and work flow. What does your company do? What does your company sell? Which industry? Because your value as a software developer goes up exponentially if you can solve a business problem, not that clever obfuscated one-liner of yours.

Don’t just be a programmer. Be a problem solver.

So after I graduated, even with a math background, I went for a software development career. I like solving problems, and programming is one method. This guy just about sums it up:

So that’s a summary of why I’m a software developer. And now for some free advertisement for Visual Studio. I like C# and Visual Studio. Probably because of my C background. There’s Intellisense, a well documented library of the .NET Framework’s functions, and… it feels “clean”. I’m not sure how to explain that to you. I’m a simple man. I don’t need a lot of what is called developer productivity tools. Maybe I haven’t a problem to solve that requires them.

[update]
And I only have the Visual Studio Express version, not the paid one (but I bought VS2005 way back if that counts). The professional version’s a little steep in price, you know, considering my recent foray into entrepreneurship.

So what’s your story? Tell me in a comment, a blog post, or a video response.

[image by Ad Hatcher. Videos taken from Developer Stories YouTube channel]

5 April, 2010 | Written by Vincent 3 Comments

Image rotation with bilinear interpolation and no clipping

I wrote an article on image rotation with bilinear interpolation to calculate the pixel information. Commenter Jahn wanted to know if the resulting image could be obtained without clipping. I replied with the required code changes (it was fairly minimal), then I thought it might be useful to you too.

The code was cleaned up a bit, and here it is:

const int cnSizeBuffer = 20;
// 30 deg = PI/6 rad
// rotating clockwise, so it's negative relative to Cartesian quadrants
const double cnAngle = -0.52359877559829887307710723054658;
// use whatever image you fancy
Bitmap bm = new Bitmap("slantedtreetopsky.jpg");
// general iterators
int i, j;
// calculated indices in Cartesian coordinates
int x, y;
double fDistance, fPolarAngle;
// for use in neighbouring indices in Cartesian coordinates
int iFloorX, iCeilingX, iFloorY, iCeilingY;
// calculated indices in Cartesian coordinates with trailing decimals
double fTrueX, fTrueY;
// for interpolation
double fDeltaX, fDeltaY;
// pixel colours
Color clrTopLeft, clrTopRight, clrBottomLeft, clrBottomRight;
// interpolated "top" pixels
double fTopRed, fTopGreen, fTopBlue;
// interpolated "bottom" pixels
double fBottomRed, fBottomGreen, fBottomBlue;
// final interpolated colour components
int iRed, iGreen, iBlue;
int iCentreX, iCentreY;
int iDestCentre;
int iWidth, iHeight;
int iDiagonal;
iWidth = bm.Width;
iHeight = bm.Height;
iDiagonal = (int)(Math.Ceiling(Math.Sqrt((double)(bm.Width * bm.Width + bm.Height * bm.Height)))) + cnSizeBuffer;

iCentreX = iWidth / 2;
iCentreY = iHeight / 2;
iDestCentre = iDiagonal / 2;

Bitmap bmBilinearInterpolation = new Bitmap(iDiagonal, iDiagonal);

for (i = 0; i < iDiagonal; ++i)
{
	for (j = 0; j < iDiagonal; ++j)
	{
		bmBilinearInterpolation.SetPixel(j, i, Color.Black);
	}
}

// assigning pixels of destination image from source image
// with bilinear interpolation
for (i = 0; i < iDiagonal; ++i)
{
	for (j = 0; j < iDiagonal; ++j)
	{
		// convert raster to Cartesian
		x = j - iDestCentre;
		y = iDestCentre - i;

		// convert Cartesian to polar
		fDistance = Math.Sqrt(x * x + y * y);
		fPolarAngle = 0.0;
		if (x == 0)
		{
			if (y == 0)
			{
				// centre of image, no rotation needed
				bmBilinearInterpolation.SetPixel(j, i, bm.GetPixel(iCentreX, iCentreY));
				continue;
			}
			else if (y < 0)
			{
				fPolarAngle = 1.5 * Math.PI;
			}
			else
			{
				fPolarAngle = 0.5 * Math.PI;
			}
		}
		else
		{
			fPolarAngle = Math.Atan2((double)y, (double)x);
		}

		// the crucial rotation part
		// "reverse" rotate, so minus instead of plus
		fPolarAngle -= cnAngle;

		// convert polar to Cartesian
		fTrueX = fDistance * Math.Cos(fPolarAngle);
		fTrueY = fDistance * Math.Sin(fPolarAngle);

		// convert Cartesian to raster
		fTrueX = fTrueX + (double)iCentreX;
		fTrueY = (double)iCentreY - fTrueY;

		iFloorX = (int)(Math.Floor(fTrueX));
		iFloorY = (int)(Math.Floor(fTrueY));
		iCeilingX = (int)(Math.Ceiling(fTrueX));
		iCeilingY = (int)(Math.Ceiling(fTrueY));

		// check bounds
		if (iFloorX < 0 || iCeilingX < 0 || iFloorX >= iWidth || iCeilingX >= iWidth || iFloorY < 0 || iCeilingY < 0 || iFloorY >= iHeight || iCeilingY >= iHeight) continue;

		fDeltaX = fTrueX - (double)iFloorX;
		fDeltaY = fTrueY - (double)iFloorY;

		clrTopLeft = bm.GetPixel(iFloorX, iFloorY);
		clrTopRight = bm.GetPixel(iCeilingX, iFloorY);
		clrBottomLeft = bm.GetPixel(iFloorX, iCeilingY);
		clrBottomRight = bm.GetPixel(iCeilingX, iCeilingY);

		// linearly interpolate horizontally between top neighbours
		fTopRed = (1 - fDeltaX) * clrTopLeft.R + fDeltaX * clrTopRight.R;
		fTopGreen = (1 - fDeltaX) * clrTopLeft.G + fDeltaX * clrTopRight.G;
		fTopBlue = (1 - fDeltaX) * clrTopLeft.B + fDeltaX * clrTopRight.B;

		// linearly interpolate horizontally between bottom neighbours
		fBottomRed = (1 - fDeltaX) * clrBottomLeft.R + fDeltaX * clrBottomRight.R;
		fBottomGreen = (1 - fDeltaX) * clrBottomLeft.G + fDeltaX * clrBottomRight.G;
		fBottomBlue = (1 - fDeltaX) * clrBottomLeft.B + fDeltaX * clrBottomRight.B;

		// linearly interpolate vertically between top and bottom interpolated results
		iRed = (int)(Math.Round((1 - fDeltaY) * fTopRed + fDeltaY * fBottomRed));
		iGreen = (int)(Math.Round((1 - fDeltaY) * fTopGreen + fDeltaY * fBottomGreen));
		iBlue = (int)(Math.Round((1 - fDeltaY) * fTopBlue + fDeltaY * fBottomBlue));

		// make sure colour values are valid
		if (iRed < 0) iRed = 0;
		if (iRed > 255) iRed = 255;
		if (iGreen < 0) iGreen = 0;
		if (iGreen > 255) iGreen = 255;
		if (iBlue < 0) iBlue = 0;
		if (iBlue > 255) iBlue = 255;

		bmBilinearInterpolation.SetPixel(j, i, Color.FromArgb(iRed, iGreen, iBlue));
	}
}
bmBilinearInterpolation.Save("rotatedslantedtreetopsky.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);

In the original article, the resulting image had the same dimensions as the source image. This meant that after rotation, some pixel information would be lost. To counter that, we make the dimensions of the resulting image “large enough”.

Considering that this is a rotation operation with an arbitrary angle, when all possible rotations of the source image are placed on top of one another, you get a smudgy circle. Not sure if you can visualise that. So the resulting image must be able to contain a circle, and because our images are stored in rectangular format, we need a square. That’s why in the code, I didn’t use a separate X and Y component of the dimensions, because they’re the same.

The width of the square has to be, at the minimum, the diagonal of the source image (which you can use Pythagoras’ Theorem to calculate). I added a buffer (of 20 pixels) to ensure that our square can contain the resulting image.

Here’s the source image:
Slanted treetop and sky

And here’s the resulting image:
Rotated slanted treetop with sky

Alright, have fun with the code.

Next Page →