Warp, bend, squeeze and transform windows with CGSSetWindowWarp


How would you like to have the power to squish, warp and transform windows to your liking? With the private Core Graphics function CGSSetWindowWarp, you can!

Warped clocks

The function CGSSetWindowWarp is part of the private portion of Core Graphics. Although its just as obscure and undocumented as the rest of the private parts of the framework, there has been some discovery and progress into its usage. Erling Ellingsen has written (a while ago) about CGSSetWindowWarp on his blog, and he also shows some very interesting screenshots of what’s possible.

transformed windowstransformed windows

So, how do you use this incredible function in your Cocoa (or Carbon) application?

CGSSetWindowWarp Usage

Including the header

Whenever you use private Core Graphics functions, you generally need to #import either one of CoreGraphicsServices.h or CGSPrivate.h (available for download with my Core Graphics ebook). However, if you’re not interested in using the rest of the Core Graphics API, you can get away with simply adding the following to your main class’ header file:


typedef struct CGPointWarp {
        CGPoint local;
        CGPoint global;
}CGPointWarp;

typedef int CGSConnectionID;
typedef int CGSWindowID;

extern CGSConnectionID _CGSDefaultConnection();

extern CGError CGSSetWindowWarp(CGSConnectionID, CGSWindowID, int w, int h, CGPointWarp mesh[w][h]);
extern OSStatus CGSGetScreenRectForWindow(CGSConnectionID cid, CGSWindowID wid, CGRect *outRect);
extern OSStatus CGSGetWindowBounds(CGSConnectionID cid, CGSWindowID wid, CGRect *bounds);

As you’ve probably guessed, the function that actually applies the mesh transform is CGSSetWindowWarp. You can create a mesh in two different ways.

Creating the mesh

To create a mesh, we need to specify how many points we’re going to take for our grid length-wise and width-wise. Something like this:

#define H 64
#define W 5

We’ll also need to refer to the position and size of the frame so the following code in the method you’re writing this will give us access to the frame of the NSWindow:

CGRect frame;
CGSGetWindowBounds(cid, [window windowNumber], &frame);

The use of CGSGetWindowBounds ensures we get the apparent frame rather than just the frame computed by our code as we’ll be applying various transformations.

The first way to create a mesh requires more labor (yes, its tiring to code) but gives you greater control over the mesh and the transformations applied to the NSWindow.

Manual mesh

CGPointWarp meshes[H][W] =
{
{ {{0, 0},{10, 10}}, {{200,0},{180,12}} },
{ {{0, 50},{0,50}}, {{200,50},{200,50}} },
{ {{0,100},{0,100}}, {{200,100},{200,100}} },
{ {{0,150},{0,150}}, {{200,150},{200,150}} },
{ {{0,200},{0,200}}, {{200,200},{200,200}} },
};

Here, you specify firstly the coordinates of the point which you’re taking, and where you want to move that point. The first line in the above code takes (0,0) and moves it to (10,10) and moves (200,0) to (180,12). However, the first set of coordinates are local coordinates, and the second set are global. (ie., the first set are on the plane of the window, while the second set use the screen as the plane. This is to allow the window to be stretched out beyond the scope of the current frame.) We could solve this issue by using:


int x = frame.origin.x;
int y = frame.origin.y;
CGPointWarp meshes2[H][W] =
{
{ {{0, 0},{x, y}}, {{200,0},{x+200,y}} },
{ {{0, 50},{x,y+50}}, {{200,50},{x+200,y+50}} },
{ {{0,100},{x,y+100}}, {{200,100},{x+200,y+100}} },
{ {{0,150},{x,y+150}}, {{200,150},{x+200,y+150}} },
{ {{0,200},{x,y+200}}, {{200,200},{x+200,y+200}} },
};

The preceding code just warps the window of size 200×200 to its current position so no change is observable. If we wanted to stretch out the middle of the window horizontally, we could use a mesh in the form:


int x = frame.origin.x;
int y = frame.origin.y;
CGPointWarp stretchThroughMiddleMesh[H][W] =
{
{ {{0, 0},{x, y}}, {{200,0},{x+200,y}} },
{ {{0, 50},{x-10,y+50}}, {{200,50},{x+200+10,y+50}} },
{ {{0,100},{x-20,y+100}}, {{200,100},{x+200+20,y+100}} },
{ {{0,150},{x-10,y+150}}, {{200,150},{x+200+10,y+150}} },
{ {{0,200},{x,y+200}}, {{200,200},{x+200+10,y+200}} },
};

We’ve taken the side where x=0 and moved x further back along the middle, and done the opposite on the opposite side (where x=200). This results in the NSWindow being warped to have a bulge in the middle. warp bulge

Again, this would only look like the screenshot on a window with a height and width of 200. For a window that’s a different size, you’ll need to change the values to match the height and width of the window. For the y-values in the mesh (x, x+50, x+100, etc. in the different rows), the numbers are determined by how many points you’ve taken (your value for H) and the width of the window. The general formula you would use would be x + ( frame.size.height / (W-1) ). You’ll see this more clearly in the second method.

Mesh through iterative formulae

The second method to create a mesh is to run a loop to configure your individual points. Here’s a basic example that creates a mesh that would be identical to the current state of the window:


int h;
int w;
CGPointWarp meshes[H][W];
for (h = 0; h < H; h++) {
    for (w = 0; w < W; w++) {
        CGPointWarp point;
        point.local.x = w * (frame.size.width / (W - 1));
        point.local.y = h * (frame.size.height / (H - 1));
        point.global.x = point.local.x + frame.origin.x;
        point.global.y = point.local.y + frame.origin.y;

        meshes[h][w] = point;
    }
}

Obviously we want to transform the window somehow. We need to apply some sort of mathematical function to the points to make them deviate from their original positions or coordinates. (This is the part where you start to wish you’d paid more attention in math class).

Lets say we want to skew the window using the x-axis. Each successive x-value needs to be greater than the previous one. Since we’re looping through each point, at any time in the inner for loop, the values of the current point are (w,h). (In Cartesian coordinates the x value is first, followed by the y value) In order to increase each successive value for x, we need to add something. But, we need to add more than what we did the previous time. The easiest way to do this without adding any new variables is to use the value that we’re incrementing each time - the x value itself. So, for a skewed window, we have the following mesh:


int h;
int w;
CGPointWarp meshes[H][W];
for (h = 0; h < H; h++) {
    for (w = 0; w < W; w++) {
        CGPointWarp point;
        point.local.x = w * (frame.size.width / (W - 1));
        point.local.y = h * (frame.size.height / (H - 1));
        point.global.x = point.local.x + frame.origin.x + 15*h;
        point.global.y = point.local.y + frame.origin.y;

        meshes[h][w] = point;
    }
}

The skewed window would like this when the mesh was applied: Skews CGSWarp

Applying the mesh

The transform mesh was really the hard part. Applying the transformation mesh (or grid) to a window is as easy as:


CGSSetWindowWarp(_CGSDefaultConnection(), [window windowNumber], W, H, meshes);

The possibilities of this are really amazing. I’ve always wanted to write an NSWindow subclass that bent and reduced in opacity as it was dragged, imitating XGL. With CGSSetWindowWarp and Core Graphics, I’m sure it’ll be a breeze. Or what about an virtual universe-style Finder cross Desktop, sort of like 3DOSX? You could even imitate other effects with this, such as the cube, but still get live updating in your windows - a limitation present in the Core Graphics transition effects.

For a source code, and a further explanation, see Kevin Ballard’s post on the subject of CGSSetWindowWarp.

Warped using CGSSetWindowWarpWarped clocksWarped clocks

Warped clocksWarped clocks

Did you find this useful? Or do you have any other method that you use for eye candy?


Back to Top ↑

6 Comments so far

Leave a comment
  1. 1

    It’s a shame I gave up LSD:) Nice writeup!

  2. 2

    I must admit I was surprised to open up this post and see a screenshot of my IronCoder entry staring back at me, with a link to my blog :)

    P.S. you typoed a rel=”external” as rel=”exernal”

  3. 3

    Thanks. :)

    I fixed the typo, and changed:

    just as obscure and undocumented as the rest of the framework

    to

    just as obscure and undocumented as the rest of the private parts of the framework

    Thanks to Scott Stevenson for pointing that out, again. I’ve lost count of how many times I’ve done that.

    Does anyone know of any app that uses CGSSetWindowWarp, or other private Core Graphics functions? I know Quicksilver uses a lot of them, and obviously the Desktop managers need to use them. Makes me think that perhaps Apple had planned for Spaces since OS 10.1.

  4. 4

    hi, is there any possibility to rotate a screen on the axis that goes in/out of the screen? ie set a whole screen to portrait mode for viewing on a rotated display?
    of course, the mouse would also have to be transformed, but i’m fairly sure that’s possible too, as i’ve written code before that dislocates the cursor from the mouse, and allows controlling the cursor separately.
    i don’t have time to investigate myself unfortunately, but it’d be fantastic if you could do these things with your knowledge of core graphics.

  5. 5

    You mean rotate the entire screen like the iPhone?

RSS feed for comments on this post. TrackBack URI

Leave a comment

Comments may be edited for formatting.