Warp images arbitrarily in C#, Part 2

The post Warp images arbitrarily in C# explains how to warp an image but omits the method that maps a pixel (x1, y1) back to a pixel (x0, y0) in the original image. This post describes that method.

If you know what transformation you want to make to go from the source image to the result image, it can be difficult in practice to invert that transformation. Fortunately a lot of transformations produce a nice result if you simply use them directly, possibly reversing some signs. For example, consider the twist transformation shown in the picture. The idea of the transformation is to rotate a pixel in the original image by an amount that depends on the distance between that pixel and the image's center. Something like Angle = K * Dist where K is a scale factor and Dist is the pixel's distance to the center.

If you plug in the equations for the distance between the point and the image's center, translate the point to the center, perform the rotation, and translate the point back to where it belongs, you get a non-trivial series of programming statements similar to this:
const double K = 100.0;
const double offset = -pi_over_2;
double dx, dy, r1, r2;

dx = x1 - xmid;
dy = y1 - ymid;
r1 = Math.Sqrt(dx * dx + dy * dy);
if (r1 == 0)
{
    x0 = 0;
    y0 = 0;
}
else
{
    double theta = Math.Atan2(dx, dy) - r1 / K - offset;
    x0 = r1 * Math.Cos(theta);
    y0 = r1 * Math.Sin(theta);
}
x0 = x0 + xmid;
y0 = y0 + ymid;

This translates the point (x0, y0) in the original image to a point (x1, y1) in the output image, but how do you invert this code to map a point (x1, y1) back to a source point (x0, y0)? Although you can probably do it if you try hard enough (I would try taking the inverse of the transformation matrix that represents this mapping), there's an easier way.

Instead of trying to unwind the code, think about what the code does. In this case, the forward mapping rotates points around the center of the image. Geometrically to undo that transformation you simply rotate points in the other direction. Now instead of trying to figure out how to undo the twist code, you simply use it. You can reverse the sign of the angle of rotation if you like but either way you get a nice result.

For another example, suppose the forward mapping adjusts a pixel's Y coordinate by a value that depends on the sine of the X coordinate. That transformation makes the image wavy as shown in the picture on the right.

Again the inverse transformation would be confusing and would probably involve arcsines. But if you consider the transformation geometrically, it's just a vertical shift depending on the X coordinate. You can undo it by applying the same vertical shift in the opposite direction. That means you can use a relatively simple inverse transform that uses the sine function instead of arcsines.

The following code shows the MapPixel method that this example uses to map the output pixel (x1, y1) to an input pixel (x0, y0).
// Map the output pixel (x1, y1) back to the input pixel (x0, y0).
private static void MapPixel(WarpOperations warp_op, double xmid,
    double ymid, double rmax, int x1, int y1, out double x0, out double y0)
{
    const double pi_over_2 = Math.PI / 2.0;
    const double K = 50.0;
    const double offset = -pi_over_2;
    double dx, dy, r1, r2;

    switch (warp_op)
    {
        case WarpOperations.Identity:
            x0 = x1;
            y0 = y1;
            break;
        case WarpOperations.FishEye:
            dx = x1 - xmid;
            dy = y1 - ymid;
            r1 = Math.Sqrt(dx * dx + dy * dy);
            if (r1 == 0)
            {
                x0 = xmid;
                y0 = ymid;
            }
            else
            {
                r2 = rmax / 2 * (1 / (1 - r1 / rmax) - 1);
                x0 = dx * r2 / r1 + xmid;
                y0 = dy * r2 / r1 + ymid;
            }
            break;
        case WarpOperations.Twist:
            dx = x1 - xmid;
            dy = y1 - ymid;
            r1 = Math.Sqrt(dx * dx + dy * dy);
            if (r1 == 0)
            {
                x0 = 0;
                y0 = 0;
            }
            else
            {
                double theta = Math.Atan2(dx, dy) - r1 / K - offset;
                x0 = r1 * Math.Cos(theta);
                y0 = r1 * Math.Sin(theta);
            }
            x0 = x0 + xmid;
            y0 = y0 + ymid;
            break;
        case WarpOperations.Wave:
            x0 = x1;
            y0 = y1 - 10 * (Math.Sin(x1 / 50.0 * Math.PI) + 1) + 5;
            break;
        case WarpOperations.SmallTop:
            dx = xmid - x1;
            dx = dx * ymid * 2 / (y1 + 1);
            x0 = xmid - dx;
            y0 = y1;
            break;
        case WarpOperations.Wiggles:
            dx = xmid - x1;
            dx = dx * (Math.Sin(y1 / 50.0 * Math.PI) / 2 + 1.5);
            x0 = xmid - dx;
            y0 = y1;
            break;
        case WarpOperations.DoubleWave:
            x0 = x1 - 10 * (Math.Sin(y1 / 50.0 * Math.PI) + 1) + 5;
            y0 = y1 - 10 * (Math.Sin(x1 / 50.0 * Math.PI) + 1) + 5;
            break;
        default:    // Flip vertically and horizontally.
            x0 = 2 * xmid - x1;
            y0 = 2 * ymid - y1;
            break;
    }
}

This code simply uses a switch statement to determine which warping transformation it should use and then returns an appropriate result. You can look through the code run the program to see the corresponding result to figure out how each works. Experiment with the code to invent new transformations. You might try changing the constants K and offset to see how that affects the transformations.

If you find any new and interesting transformations, email me and I'll post the most interesting ones.

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.