Monday, June 14, 2010

#9, Graphics Programming, Part 3

It's summer and from sunny days to travel plans to tons of projects that you have left for summer to do, there's not much time to explore OSX, although I've been using my MBP all the time. But anyway, I'm following the plan, and this week I'll continue with graphics programming by trying some basic image functions; things such as displaying an image and drawing on it.

We learned about some basic concepts in graphics programming last week. They were Rect, Point, and Color. This week, I introduce two new ones which are as fundamental as those: Path and Image. We will see which Cocoa (and in fact Quartz) classes control them and how.

==========
Path
==========

A Path is a line (including straight and curved lines, arcs, rectangles, etc) that is defined by points and some math to connect them. Paths can draw themselves to a view (see last week post to remember what a view is) and they can be changed or combined. NSBezierPath is the Cocoa class that represents these lines, or in general Bezier Curves (parametric curves with control points, http://en.wikipedia.org/wiki/Bezier_curve). Let's look at some examples of creating and drawing Path objects. The code can go inside the drawRect method of your view class from last week.

The first example draws a straight line between two points with a gray color:

//create start and end points
NSPoint startPoint = { 21, 21 };
NSPoint endPoint = { 128,128 };

//create the path object
//the bezierPath static method in NSBezierPath class returns a new and empty object
NSBezierPath * path = [NSBezierPath bezierPath];

//define the path's start and end, and the path type (a straight line)
[path moveToPoint: startPoint];
[path lineToPoint: endPoint];

//set the line width for drawing
[path setLineWidth: 4];

//define the current colour
[[NSColor grayColor] set];

//draw the path
[path stroke];

Now let's make things a little more complicated and draw a curved line with two control points. All we need to do is to replace the call to lineToPoint with a call to curveToPoint which requires two parameters. These parameters are Bezier control points and we use NSMakePoint function to create them:

[path curveToPoint: endPoint
controlPoint1: NSMakePoint ( 128, 21 )
controlPoint2: NSMakePoint ( 21,128 )];


Just like we could fill a Rect with a colour, we can fill a Path with a colour. Simply call the method fill before your call to method stroke:

[[NSColor whiteColor] set];
[path fill];

//draw the path
[path stroke];

And here is the code for creating a rectangular Path:

//create a Rect
NSPoint origin = { 21,21 };
NSRect rect;
rect.origin = origin;
rect.size.width = 128;
rect.size.height = 128;

//create the Path using a call to bezierPathWithRect method
NSBezierPath * path;
path = [NSBezierPath bezierPathWithRect:rect];

Or a circular Path:

NSBezierPath * path = [NSBezierPath bezierPath];
NSPoint center = { 128,128 };

[path moveToPoint: center];
[path appendBezierPathWithArcWithCenter: center
radius: 64
startAngle: 0
endAngle: 321];

.NET framework provides similar functionality through Point, Color and Pen classes and also families of Draw and Fill methods in the Graphics class. Here is an example in C#:

System.Drawing.Pen myPen = new System.Drawing.Pen(System.Drawing.Color.Red);
System.Drawing.Graphics formGraphics = this.CreateGraphics();
//draw line by specifying X and Y for start and end
formGraphics.DrawLine(myPen, 0, 0, 200, 200); //from (0,0) to (200,200)
//we could also use a Point object
//draw a Bezier curve using four points
formGraphics.DrawBezier(myPen, startPoint, controlPoint1, controlPoint2, endPoint);


==========
Image
==========

Next stop is the Image which probably is the most used object in 2D graphics. The NSImage class represents 2D images and handles drawing at different sizes and opacity levels, and also disk read/write. Here is how we do the two basic operations, reading from file and drawing:

//create a Rect to draw the image in
NSPoint imageOrigin = NSMakePoint(0,0);
NSSize imageSize = {100,100};
NSRect destRect;
destRect.origin = imageOrigin;
destRect.size = imageSize;

//load the image
NSString * file = @"/Library/Desktop Pictures/Plants/Leaf Curl.jpg";
NSImage * image = [[NSImage alloc] initWithContentsOfFile:file];

//draw the image into the Rect
[image drawInRect: destRect
fromRect: NSZeroRect
operation: NSCompositeSourceOver
fraction: 1.0];

One interesting part of this process is the fraction in drawInRect method which controls the opacity.

Before we draw we can "flip" the image:
[image setFlipped:YES];
This is due to the fact that views can be standard or flipped (i.e. the origin being bottom-left or top-left).

We can combine images and paths to for example draw a frame around the object. Simply add the path code after the image code.

In .NET, the Image class and DrawImage method in Graphics class provide similar functionality. For example the following code draws an image into a parallelogram (doesn't need to be a rectangle):

// Create image.
Image newImage = Image.FromFile("SampImag.jpg");
// Create parallelogram for drawing image.
Point tlCorner = new Point(100, 100); //top-left
Point trCorner = new Point(550, 100); //top-right
Point blCorner = new Point(150, 250); //bottom-left
Point[] destPara = {tlCorner, trCorner, blCorner}; //bottom-right can be calculated
// Draw image to screen.
e.Graphics.DrawImage(newImage, destPara);

For drawing, .NET support opacity levels through alpha-blending (alpha being the fourth part of a given colour after red, green, and blue). DrawImage has other versions that support image attributes (including opacity).



As we can see Cocoa and .NET provide similar functionality through similar (and sometimes a little different) ways. I'm not sure about you, but I didn't see any particular advantage in either method. There are some details in both that again are a little easier or more powerful in one or another, but for basic 2D graphics programming, so far there is no winner!


I'LL BE BACK!

No comments:

Post a Comment