Monday, August 28, 2006

Graphics: Qt vs Cocoa

The logic of how things work in Cocoa is quite different from most other OO toolkits, especially when it comes to graphics. Here I will give a comparison to how one does some basic graphics operations in Cocoa as opposed to Qt.

I might have chosen to compare Cocoa to Java or .NET frameworks but I am most familiar with Qt. Qt graphics model is quite similar to that use in Java and .NET so the differences explained here should be of interest to Java and C# programers as well.

Introduction

Cocoa and Qt have similar image models for graphics. Both are able to describe images independent of output device (device on which image is displayed) and information about points in drawings (vertices) are retained in the model so that different transformations can be applied to it (e.g. affine transformations.)

Programmin models

However their programming models are different. A programming model can be statefull or stateless and procedure oriented or object oriented. E.g. OpenGL has a statefull and procedure oriented programming model while DirectX has a stateless and object oriented programming model.

In a statefull programming model all drawing operations are affected by the state of the graphics system. E.g. a command to draw a box might be drawBox(x, y, width, height). This would draw a box with current color. The color is then a state in the graphics system. In a stateless system the command would be drawBox(x, y, width, height, color).

The reason why many people find Cocoa confusing at first is that it uses a statefull and object oriented programming model. This is uncommon as Qt, Java and .NET all use a stateless object oriented programming model. In this respect Cocoa has a lot of similarities with OpenGL.

Setting drawing target

In Qt drawing commands change the image or graphics of a QPaintDevice. Typically one of the subclasses like QWidget is used. Below is an example on how one can draw a line of thickness 2 and blue color through three points p1, p2 and p3 on some arbitrary widget returned by getCanvas().

QWidget* w = getCanvas(); ''// Returns our drawing surface
QPainter paint(w);

QPen pen(Qt::blue);
pen.setWidthF(2.0);
paint.setPen(pen);

QPoint p1(x1, y1);
QPoint p2(x2, y2);
QPoint p3(x3, y3);

paint.drawLine(p1, p2);
paint.drawLine(p2, p3);

As one can see the surface to draw on is provided specifically to the QPainter object as well as the pen and color to use. The graphics system is not in any state. The result of the drawing operations is determined exclusively by the objects involved an their attributes.

Below is an example on how to do the same with Cocoa:

NSView* view = getCanvas(); // Returns our drawing surface
NSBezierPath* path = [[NSBezierPath alloc] init];

[[NSColor] blueColor] set];
[path setLineWidth:2.0];

NSPoint p1 = NSMakePoint(x1, y1);
NSPoint p2 = NSMakePoint(x2, y2);
NSPoint p2 = NSMakePoint(x3, y3);

[path moveToPoint:p1];
[path lineToPoint:p2];
[path lineToPoint:p3];

[view lockFocus];
[path stroke];
[view unlockFocus];

As one can see there are some noticeable differences. There is no one object that represents the state of the graphics system like QPainter. NSBezierPath might look like it but it merely keeps track of points on a path and how the lines that connect the points are. No color information is passed to NSBezierPath in the form of a NSColor object nor is the surface on which to draw specified as in the Qt example.

Instead the state of the graphics system itself is changed. [color set] is used to change the color state of the graphics system. Likewise [view lockFocus] is used change the state for current drawing area in the graphics system. Usually when a views method to update its area is called, the state of the drawing area has already been set to that view. So most of the time the user does not have to call lockFocus himself.

Drawing bitmaps

One are that is difficult to understand once one gets started with Cocoa graphics is how to deal with bitmaps. It is confusing precisely because when one is not used statefull programming models for graphics it is not obvious how one draws into a bitmap.

Both Qt and Cocoa have two different image formats. However comparing them is difficult because there is no one to one correspondence in functionality. Qt has QPixmap and QImage. QPixmap is used for drawing images that will go on screen. Because graphics can be created in many different ways on screen individual pixel access is not possible. QImage on the other hand exists off screen in memory an allows individual pixel manipulation. However to put a QImage on screen it has to be converted to a QPixmap

In Cocoa there is a similar situation. NSImage vaguely coresponds to QPixmap. You can not access its individual pixels but you can draw into it and display it on screen. NSBitmapImageRep corresponds roughly to QImage. You can access its pixels individually and set up exactly how pixels are stored, how many color components are used etc. However until recently you could not draw directly into it. Instead you would draw to a NSImage. The reason for this is that NSImage can represent an offscreen window, while NSBitmapImageRep is just a bunch of bytes in memory. Windows area can be drawn on by graphics hardware so they can be represented in any number of ways. They could exist in special graphics memory or on a remote machine. Thus accessing individual pixels on an NSImage makes no sense. However giving drawing commands does because the graphics hardware have drawing commands implemented to do it fast. Doing drawing on NSBitmapImageRep does not make sense because it is not accessible to graphics hardware and drawing can be done by graphics hardware.

Below is an example on how to manipulate the pixels in a QPixmap

Pixmap pixmap = getImage(); // Get some image we want to manipulate
QImage img = pixmap.toImage();

for (int y=0; y < img.height(); ++y) {
''''for (int x=0; x < img.width(); ++x) {
''''''''QRgb pix = img.pixel(x,y);
''''''''doSomethingWithPixel(&pix);
''''''''img.setPixel(x,y,pix);
''''}
}

The code below shows how to do the same thing in Cocoa. That is, to manipulate the pixels in a NSImage. Notice how you must lockFocus on the NSImage to for the pixel grabbing to occur on that particular image.

NSImage* pixmap = getImage(); // Get some image we want to manipulate
NSRect rect = NSMakeRect(NSZeroPoint, [pixmap size]);

[pixmap lockFocus]; // Make pixmap target for drawing commands
NSBitmapImageRep* img = [[NSBitmapImageRep alloc] initWithFocusedViewRect:rect];
[pixmap unlockFocus];

for (int y=0; y < [img pixelsHigh]; ++y) {
''''for (int x=0; x < [img pixelsWide]; ++x) {
''''''''NSColor* pix = [img colorAtX:x y:y];
''''''''doSomethingWithPixel(pix);
''''''''[img setColor:pix atX:x y:y];
''''}
}

[pixmap addRepresentation:img];

Drawing images on screen

To round I need to show how you draw images on screen, or more specifically in a window. A QWidget in Qt's case and a NSView in Cocoa's case. Below we can see how to draw rectangular area within an image given by srcRect inside a rectangular area inside a window (QWidget) given by dstRect

QWidget* w = getCanvas(); ''// Returns our drawing surface
Pixmap pixmap = getImage(); // Return some image we want to draw

QPainter paint(w);

QRectF dstRect(x1, y1, width1, height1) // Area of window to draw into
QRectF srcRect(x1, y1, width2, height2); // Area of image to draw

paint.drawPixmap(dstRect, pixmap, srcRect);

Below we have the corresponding code for Cocoa. Notice that the drawing method is called on the NSImage itself. This does not however mean that drawing is performed inside the image as Qt, Java and C# programmers would easily assume. The target for drawing commands is always the surface/canvas which has focus.

NSView* w = getCanvas(); ''// Returns our drawing surface
NSImage* pixmap = getImage(); // Return some image we want to draw

NSRect dstRect = NSMakeRect(x1, y1, width1, heigth1); // Area of window to draw into
NSRect srcRect = NSMakeRect(x2, y2, width2, heigth2); // Area of image to draw

[w lockFocus];
[pixmap drawInRect:dstRect fromRect:srcRect operation:NSCompositeCopy fraction:1.0];
[w unlockFocus];

Final thoughts

It should be clear that drawing in Cocoa takes some time getting used to for Qt, Java or C# programmers. I have only scratched the surface in this post. From my own experience using both Java and Qt, it is a lot easier to get up to speed on graphics in Qt and Java at first.

However as is typical with everything else with Cocoa it might not be fast to get started but when you dwell into more complicate things, that is when Cocoa start to shine. Likewise with graphics. My own impression from using it (although I am no wizard in Cocoa graphics) is that when it comes to more complicated graphics Cocoa is much more capable than Qt.

It is also my impression that for short examples as given here Qt and Java would usually require less code, but when the graphics is more complicated less code is required in Cocoa.

However I must say that dealing with bitmap graphics seems overly complicated at times in Cocoa. Probably owing a lot to the fact that NEXTStep had totally vector based graphics. The graphics system would output post script drawing commands to the window server and not individual pixels. Basically vector graphics was made easy at the expense of pixel manipulation and raster graphics.

Thoughts on Objective-C 2.0

One of the most exciting changes coming in Mac OS X Leopard, is the new xCode 3.0 with a major update of the Objective-C language. While I think Objective-C is a great programming language and Cocoa is a great framework, it is getting a little bit long in the tooth in a few places.

This has attracted considerable bashing from different people. Nitesh Dhanjani of O'Reilly for instance started a big debate about whether Apple should offer more choices to developers.

The basic argument is that Microsoft is way ahead of the curve with C# and the .NET framework. .NET allows developers to choose any language in theory, thus giving them more choice. This is not something apple offers at the moment.

However with Objective-C 2.0 it seems like Apple has narrowed the gap to more modern languages like C# and Java. It is not official yet but it seems like Objective-C will offer:

  • Optional garbage collection. This was actually an option in Tiger, but toggling it didn't do anything.
  • for each statement. Which makes iterating through objects in containers easier. This is similar to the C# for each.
  • Properties. You don't need to explicitly use setter and getter methods. If you access or set a variable, access methods will be used if they are defined.
  • Method attributes. Not so sure about this but apparently it is similar to what is found in C#.

Here are some examples of the new syntax. The for each syntax:

NSArray *arr = randomListOfStrings();
for (NSString *s in arr) {
  NSLog("%@\n", s);
}

And here is an example of properties:

@interface MyClass : NSObject {
  int   foo;
  float baz;
}

@property int foo;
@property(getter=_getBaz,setter=_setBaz) float baz;

@end

@implementation MyClass

@synthesize foo;

- (int)_getBaz { return baz; }
- (void)_setBaz: (float)newBaz { baz = abs(newBaz); }

@end

int main(void) {
  MyClass *m = [MyClass new];
  m.baz = -5.0;
  printf("%f\n", m.baz); /* -&gt; 5.0000 */
}

I think this means that Mac developers don't have to consider themselves second class citizens. Although I think it never was that bad. A lot of the advantages of .NET and C# are highly theoretical.

Even though you can make any language that language is still limited in functionality to that of the IL code and the .NET framework. The Cocoa framework exploits a lot of the particular strengths of the Objective-C language. It would be difficult to make a framework similar to Cocoa within the .NET world.

So far it seems like most .NET developers are using C#. The other major .NET languages offer little extra functionality or benefits.

Even if Apple does not have an IL type language on which to build other languages they are not really that trapped in language offerings. Because of the highly dynamic nature of Objective-C which is based on message passing instead of method invocation. Other dynamic languages can easily be made to interface with Objective-C classes. Some examples are Ruby Cocoa and PyObjC, which is a Python bridge to Cocoa. One only needs to create a bridge or in other words code that translates the message passing in Objective-C to that of the other language. E.g. in Java there needs to be defined a type for each Objective-C class to maintain the bridge. Unknown Objective-C classes can not be used. For more in-depth look at the problems with bridging Objective-C and Java look at cocoadev.

Performance

Another important point is that I think Objective-C is simply a much more versatile language than C#. Although high performance can be squeezed out of C# it is not nearly as easy. "Easy" is a keyword here, because no language can really do anything another language can't. All popular programming languages today are Turing complete. The difference is then about with what flexibility, ease and performance one can do a certain task.

In a lot of language you can create modules written in C for critical sections and call them from the "mother" language. You can do this in Java through Java native calls. In Ruby you can add your own classes written in C. In C# you can mix managed and unmanaged code. E.g. you can have certain sections written in plain C++.

However the ease which one does this differs. Objective-C is a superset of C and interacts with it easily. Writing performance intensive parts of the program is thus easy and straight forward. One simply tries to use as much plain C as possible and as little object oriented features.

The same can not be said about .NET. I have learned the hard way that mixing managed and unmanaged code is no trivial task. It looks easy in theory and doesn't seem that hard when one does it with small example code. But as soon as you have to deal with a large real world application, problems start piling up.

This means that for performance critical tasks it will be much easier to fine tune a Objective-C application than a .NET application.

Missing features from Objective-C

So if the rumors are right, what will be missing from Objective-C to make it competitive with C# and other modern languages? I could actually only thing of one thing and that is support for namespaces. At the moment this is solved by prefixing class and function names with two letters, to distinguish them from classes and functions with the same name.

Some might argue templates or generics is missing but that is only something that is needed on a static language like C++, Java and C#. In Objective-C data types (except primitives) can be easily inserted and accessed from a container class. No up-casting is needed.

Conclusion

Mac development is going to be better than ever. The only thing that I miss now is a better IDE. I think the debugger in xCode has a lot of problems. It should be improved and so should code completion. Code navigation and re-factoring support could also be improved. At the moment I use TextMate a lot for my coding because it is a lot faster to jump around in code. Apple+T brings up a window that does name completion of files so I can jump to a new file with some quick typing. it also does tabs.

Perhaps Apple should simply improve the integration of the code editor with third party tools. For instance allows some hooks so that after a failed build one can jump to errors in an external code editor.

Also they should do something to reduce the amount of windows that are open at any given time. I do like multiple windows because it allows you to organize your work more when you have a lot of screen real-estate. But at the moment it just seem to become too many windows to keep track of: debug window, code window, run window, console window, project window etc.

If not reducing the number of windows at least provide a better way of managing them (although I guess Spaces might solve a lot of the problem).

My last wish is that Cocoa method names was a bit shorter. Do we really need:

[array insertObject:obj atIndex:2];

Isn't it just as readable to simply write:

[array insert:obj at:2];

Actually I find it almost hard to read Objective-C code at times because the Cocoa method names are so long that you can't actually glaze at them to see that they say. E.g. "thisIsAVeryLongMethodNameIndeed" takes some time to read. Of course this is all wishful thinking because it is too late to change the names not. But one can always dream.