Archive for the 'Programming' Category

Formatting Objective-C code with the HTML code tag

You might have noticed, especially given the last post, that the code formatting on this blog leaves much to be desired. I’m trying to rectify that, but I’m not quite sure how to do it.

I’ve always assumed that when presenting code, I should use the the <code> tag. Unfortunately, the default formatting of this tag is indeterminate at best. From trial and error I’ve determined that it’s usually best to put each line of code in its own set of <code></code> tags, otherwise formatting gets really wonked. I’ve also found, depending on the CSS, I might have to put a <br> after each line of code, otherwise my nice, pretty function all ends up on one line.

And this completely ignores indented code. There seem to be a couple of ways to making indentation work. The first is to use the white-space: pre; CSS rule and make sure the tab characters are in the <code> tags. The second is to use the text-indent CSS attribute for each line of code I want to indent.

None of these options are easy or simple, which makes me think I’m doing something wrong. Are there are any web designers/HTML coders in the audience that know the proper way of doing this? Surely it’s not intended to be this painful.

The problem with formatting code on this blog has led me to the conclusion that I’m probably going to have to ditch my current WordPress theme. Sure it’s pretty, but it does float: right on my images with a rule that has high specificity, making it very difficult to not float my images to the right, which is what I usually want. It also has a fixed width, which doesn’t really mix well with my code examples, not to mention it doesn’t style the <code> tags well.

Unfortunately I haven’t been able to to find an appropriate WordPress theme that will fit my needs. I’d really like a two column layout, with a fluid width, and, if it’s not too much to ask, decent <code> tag handling.

How to implement a basic bitmap brush

There is not a day that goes by where I’m asked how brushes in bitmap graphics editors work. And I note this with quite some dismay. I mean, not even my fiancĂ©e asks me. She always wants to talk about our “relationship” and our “future,” never about brush stamping algorithms. Talk about insensitive.

Since none of you insensitive clods were thoughtful enough to ask me about brushes, I’ll ask myself:

So Andy, how do brushes in bitmap editors work?

Wow, what a great question. I can tell from the quality of the question that you are both smart and handsome.

Let’s get started.

Basic idea

The idea behind a bitmap brush is actually really simple. You take an image of the tip of the brush you want to draw with and repeatedly draw it, with enough frequency that it looks like you dragged the brush tip across the screen.

For example, let’s say you had a brush with a tip that looked like this:

Blue, round, 20px brush

As you can see that is a round, blue brush, 20 pixels in diameter. In Fireworks parlance, it would be a 20px hard round brush, with 0% softness.

To simulate brushing with this, you would draw the image above repeated between the two points the user dragged their mouse. A naive implementation might space out the drawing of the brush tip to the width of the brush tip:

Blue brush stamped 20 pixels apart

As you can see, there are gaps between the drawn images, so it doesn’t look so much like someone dragged a brush across the screen, as they dribbled a brush across it. An obvious improvement is to draw the brush tip only half the width of the brush apart:

Blue brush stamped 10 pixels apart

That is obviously better, but not quite it. If we increase the frequency of drawing to a quarter of the width of the brush tip, we get acceptable results:

Blue brush stamped 5 pixels apart

The method of repeated drawing an image repeatedly like this is commonly called stamping, although I’ve also heard it referred to as “dabbing.”

In general, I’m going to relate back to Fireworks for comparative functionality and parlance, although it should be similar to “that other image editor,” Photoshop. The sample code shows how to implement Fireworks four kinds of “basic” brushes, sans the texture support.

Code architecture

To go along with this article I have created some sample code that will demonstrate everything I talk about. It’s a mixture of Cocoa and Quartz, although the ideas should work in any environment; only the API calls will change. This is within reason of course: Quartz takes care of a lot of complicated things like alpha blending and antialiasing, and if your API (like QuickDraw, GDI) can’t deal with that, then you’ll have a lot more work to do.

There are three main classes that make up the sample code: CanvasView, Canvas, and Brush.

CanvasView is an NSView, and for the most part will be ignored in this article. It serves as a moderator between the Canvas and Brush objects, and doesn’t contain any real functionality. It passes on the drawRect: message to the Canvas object, and mouseDown:, mouseDragged:, and mouseUp: messages to the Brush object.

Canvas implements two graphics primitives: draw a brush at a specific point, and draw a line with a brush. i.e. It is the class that implements the stamping algorithm. It also has a method to transfer its rendered contents into an NSGraphicsContext. Just like the name implies, it represents the canvas, piece of paper, etc that the user draws onto.

The Brush class represents the tool the user draws with. As such, it takes user input (in our case only mouse events) and turns them into graphics primitives for the Canvas object. It is also responsible for generating the image of the tip of the brush, which, in this sample code, is somewhat configurable.

In this article, I’ll tackle the Canvas class first, then build on it with the Brush class. As I stated before, I’ll ignore the CanvasView class, but you can always see exactly what it does (nothing) by downloading the code.

Canvas

The Canvas class is implemented using a CGBitmapContext as its backing store. It has a – (id) initWithSize:(NSSize)size method, as well as a – (void) dealloc method. Nothing real exciting happens either place so I’ll just summarize them. The init method creates a 32-bit ARGB bitmap context at the specified size, the fills the entire context with an opaque white. The dealloc method simply releases the bitmap context.

Drawing the canvas onto a view

The Canvas class also has a drawRect:inContext: method that transfers the contents of the bitmap context into the NSGraphicsContext that the CanvasView passes in. Nothing complicated happens here either, but I’ll show it for completeness:

- (void)drawRect:(NSRect)rect inContext:(NSGraphicsContext*)context
{
	// Here we simply want to render our bitmap context into the view's
	//	context. It's going to be a straight forward bit blit. First,
	//	create an image from our bitmap context.
	CGImageRef imageRef = CGBitmapContextCreateImage(mBitmapContext);

	// Grab the destination context
	CGContextRef contextRef = [context graphicsPort];
	CGContextSaveGState(contextRef);

	// Composite on the image at the bottom left of the context
	CGRect imageRect = CGRectMake(0, 0, CGBitmapContextGetWidth(mBitmapContext),
								  CGBitmapContextGetHeight(mBitmapContext));
	CGContextDrawImage(contextRef, imageRect, imageRef);

	CGImageRelease(imageRef);

	CGContextRestoreGState(contextRef);
}

As you can see, we simply create a CGImageRef from our bitmap context and then draw it right into the provided NSGraphicsContext. Like I said, nothing terribly exciting going on yet.

Rendering a single stamp

Things get a little more interesting with the simplest graphics primitive, stampMask:at:, which draws a CGImageRef centered on a specific point. It is used by the line drawing primitive on the Canvas object as well as the Brush object directly, when handling a mouseDown: message. It’s fairly straight forward:

- (void)stampMask:(CGImageRef)mask at:(NSPoint)point
{
	// When we stamp the image, we want the center of the image to be
	//	at the point specified.
	CGContextSaveGState(mBitmapContext);

	// So we can position the image correct, compute where the bottom left
	//	of the image should go, and modify the CTM so that 0, 0 is there.
	CGPoint bottomLeft = CGPointMake( point.x - CGImageGetWidth(mask) * 0.5,
									  point.y - CGImageGetHeight(mask) * 0.5 );
	CGContextTranslateCTM(mBitmapContext, bottomLeft.x, bottomLeft.y);

	// Now that it's properly lined up, draw the image
	CGRect maskRect = CGRectMake(0, 0, CGImageGetWidth(mask), CGImageGetHeight(mask));
	CGContextDrawImage(mBitmapContext, maskRect, mask);

	CGContextRestoreGState(mBitmapContext);
}

This works how you think it would. It determines where the bottom left of the image should be positioned, such that the image’s center is at the point passed in. It then translates the context so that 0, 0 is where the bottom left of the image should be, and draws the image.

Rendering a line of stamps

Alright, now that we have all the building blocks of Canvas done, we can move on to the meat of Canvas, stampMask:from:to:leftOverDistance:, which is the method that draws a line with the given brush image. This is a decent sized function, so I’m going to cover it in parts.

First, the declaration:

- (float)stampMask:(CGImageRef)mask from:(NSPoint)startPoint to:(NSPoint)endPoint leftOverDistance:(float)leftOverDistance

  • mask is the brush image that we’re going to stamp.
  • startPoint is the starting point of the line.
  • endPoint is the ending point of the line to draw.
  • leftOverDistance is the distance of the specified line that we did not render on the previous invocation (more on this later.) This will always be the return value from the previous invocation of this function.
  • The return value is the remainder of the line that we didn’t render.

The first thing we do in stampMask:to:from:leftOverDistance: is to determine the spacing between stamps of the image:

// Set the spacing between the stamps. By trail and error, I've
//	determined that 1/10 of the brush width (currently hard coded to 20)
//	is a good interval.
float spacing = CGImageGetWidth(mask) * 0.1;

// Anything less that half a pixel is overkill and could hurt performance.
if ( spacing < 0.5 )
	spacing = 0.5;

Initially, we compute the spacing between stamps to be 1/10 of the width of the brush. In the overview, I used 1/4 of the width, but after quite of bit of trail and error, I decided that 1/10 of the width looked better. It is rather subjective; feel free to play around with this value, it often varies based on what kind of brush you have. In fact, if this were a real system, we’d ask the brush for the spacing instead of computing it here.

We also cap the lower bound of the spacing. Anything less than half a pixel is pretty much overkill and won’t really gain us anything, except for slower performance. You might run into this if you play with some of the values in Brush, which I’ll get to later.

I should also note that this code makes a major assumption: that the brush bounding box is square. i.e. CGImageGetWidth() == CGImageGetHeight(). This doesn’t mean the brush has to be actually square (it could be a circle, and by default is), but it does mean the brush currently has to be symmetrical both vertically and horizontally.

The way we’re going to plot this line is to start at the startPoint passed in, and increment the x and y components such that we cover a distance of spacing on each iteration. On each iteration of the loop, we’ll draw the image at the newly computed point.

To get the the x and y increments, we need to compute the deltas between the start and end points. This essentially computes the slope of the line:

// Determine the delta of the x and y. This will determine the slope
//	of the line we want to draw.
float deltaX = endPoint.x - startPoint.x;
float deltaY = endPoint.y - startPoint.y;

The problem is that the distance of the slope (x and y increments) computed here isn’t uniform: it could be any distance based on how far the user dragged the mouse. Since we want our increments to be spaced evenly (always a spacing distance apart), we need to normalize the x and y increments such that their distance is 1 (which is what normalization of a vector, by definition, does).

// Normalize the delta vector we just computed, and that becomes our step increment
//	for drawing our line, since the distance of a normalized vector is always 1
float distance = sqrt( deltaX * deltaX + deltaY * deltaY );
float stepX = 0.0;
float stepY = 0.0;
if ( distance > 0.0 ) {
	float invertDistance = 1.0 / distance;
	stepX = deltaX * invertDistance;
	stepY = deltaY * invertDistance;
}

Part of the computations for normalizing our slope includes computing the distance between the start and end points. We’ll need that next.

The next thing we do in the function is declare a couple of variables for calculating the offset for the next stamp. This is only used in the loop, so it’s not germane to the next part of the function we’re currently discussing, but I hate it when tutorials leave out important parts of the function, so here they are.

float offsetX = 0.0;
float offsetY = 0.0;

If you recall earlier, we were passed in the distance that our previous invocation did not cover. This time around, we want to get that part too, if possible. So add it to the total distance we want to cover in our stamping loop:

// We're careful to only stamp at the specified interval, so its possible
//	that we have the last part of the previous line left to draw. Be sure
//	to add that into the total distance we have to draw.
float totalDistance = leftOverDistance + distance;

The stamping loop is pretty simple. As stated before, it will simply cover the total distance (the left over distance from the previous invocation plus the new distance we got in the current invocation) going at increments of spacing. The basic stamping loop (and rest of the function) looks like:

// While we still have distance to cover, stamp
while ( totalDistance >= spacing ) {
	// ... increment the offset and stamp...

	// Remove the distance we just covered
	totalDistance -= spacing;
}

// Return the distance that we didn't get to cover when drawing the line.
//	It is going to be less than spacing.
return totalDistance;

I’ve included the return statement, which is directly after the end of the while statement, to make a point. Because our loop only exits if totalDistance < spacing, the return value is also always going to be less than spacing. Furthermore, because our parameter leftOverDistance is always the return value of the previous invocation, it is also always less than spacing. That will be important when we start digging around in the guts of our stamping loop.

The other thing to note here is that we stop if the next stamp would put us past the distance we were supposed to draw. i.e. We never overdraw, but we could underdraw. As you’ll see in the guts of the stamping loop, we take care to only draw at the specified spacing, so that our brush strokes are even and smooth.

Speaking of the guts of our stamping loop, let’s dig around in there. The first thing to do inside of the loop is determine the offset from the starting point to draw the next stamp at:

	// Increment where we put the stamp
	if ( leftOverDistance > 0 ) {
		// If we're making up distance we didn't cover the last
		//	time we drew a line, take that into account when calculating
		//	the offset. leftOverDistance is always < spacing.
		offsetX += stepX * (spacing - leftOverDistance);
		offsetY += stepY * (spacing - leftOverDistance);

		leftOverDistance -= spacing;
	} else {
		// The normal case. The offset increment is the normalized vector
		//	times the spacing
		offsetX += stepX * spacing;
		offsetY += stepY * spacing;
	}

As you’ll probably note, offsetX and offsetY are the two variables that we are calculating here. They accumulate as the loop continues.

The first thing we check is if we have any left over distance from the previous invocation of our function. If so, we need to handle it first. Recall that stepX and stepY have been normalized, so that their distance is one. Normally we’d multiple them by spacing so that the next stamp in the loop is spacing pixels from the previous stamp. But since we have distance we already skipped from the previous invocation, we don’t want to skip it again, so we subtract it from spacing, and multiple that by stepX and stepY.

As an example, suppose spacing was 5 pixels, and the previous invocation had 12 pixels of distance to cover. It would have 2 pixels left over, which we would receive in the current invocation as leftOverDistance. Since we already have 2 pixel distance between the last stamp point and startPoint, we only want to advance by 3 pixels in distance after startPoint before we stamp again. This keeps the distance between stamps the same, which is important. Otherwise the “ink” clumps where it’s not supposed to, and looks wrong.

At the end of the first if clause, we subtract spacing from leftOverDistance, since we handled it. Recall that leftOverDistance is always less than spacing so leftOverDistance goes to less than 0, and we don’t ever hit the if clause again in the current invocation.

The else clause is the normal case, where we just multiply the normalized vector, stepX and stepY, by spacing and add it to the offset from the start point.

Now that we’ve computed the offset from the starting point, we compute the absolute position of the stamp. It’s straight forward:

	// Calculate where to put the current stamp at.
	NSPoint stampAt = NSMakePoint(startPoint.x + offsetX, startPoint.y + offsetY);

We now have all the information we need to actually stamp the image. So the last part of the loop is simply calling the other graphic primitive on Canvas, stampMask:at:

	// Ka-chunk! Draw the image at the current location
	[self stampMask:mask at: stampAt];

And that concludes both the stampMask:from:to:leftOverDistance: message and the Canvas class. As you can tell, its fairly straight forward, with the possible exception of the code to ensure the stamps are always evenly spaced. To summarize, the Canvas class provides the basic drawing primitives for drawing a single stamp and a line of stamps. It can then render itself to a view context.

Brush

The other interesting class in the sample code is the Brush class. Its primary purpose is to tell the Canvas object where to draw lines, and construct an image of the brush for the Canvas class to use to stamp with.

Parameters

Like the Canvas class, the Brush class has init and dealloc methods. However, the init method on the Brush class is actually interesting because it allows you to do a fair amount of customization to the brush instance. The init method looks like:

- (id) init
{
	self = [super init];

	if ( self ) {
		mRadius = 10.0;

		// Create the shape of the tip of the brush. Code currently assumes the bounding
		//	box of the shape is square (height == width)
		mShape = CGPathCreateMutable();
		CGPathAddEllipseInRect(mShape, nil, CGRectMake(0, 0, 2 * mRadius, 2 * mRadius));
		//CGPathAddRect(mShape, nil, CGRectMake(0, 0, 2 * mRadius, 2 * mRadius));

		// Create the color for the brush
		CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
		float components[] = { 0.0, 0.0, 1.0, 1.0 }; // I like blue
		mColor = CGColorCreate(colorspace, components);
		CGColorSpaceRelease(colorspace);

		// The "softness" of the brush edges
		mSoftness = 0.5;
		mHard = NO;

		// Initialize variables that will be used during tracking
		mMask = nil;
		mLastPoint = NSZeroPoint;
		mLeftOverDistance = 0.0;
	}

	return self;
}

The init method is interesting because of the member data it sets. There are five you can alter, which change how the brush looks, and thus draws.

  • mRadius This is simply how big the brush is, from the center to the outside edge. As stated before, this code assumes the shape bounding box is square. Here are some variations of the mRadius variable:

    mRadius = 10, 20 pixels
    mRadius = 5, 10 pixels
    mRadius = 20, 40 pixels

  • mShape This is a CGPathRef, which describes the shape of the brush tip. Because of my limited imagination, the two examples given here are a circle and square:

    mShape = circle, Shape is circle
    mShape = square, Shape is square

  • mColor Probably the easiest to understand of the variables, this is a CGColorRef that specifies the color of the brush. Here are the three easiest examples in the RGB colorspace:

    mColor = [1.0, 0.0, 0.0], Color is red
    mColor = [0.0, 1.0, 0.0], Color is green
    mColor = [0.0, 0.0, 1.0], Color is blue

  • mSoftness This is simply a percentage, represented by a float ranging from 0.0 to 1.0. It determines how much we soften the edges of a brush. It is usually related to mHard, but they can be used independently. Some examples of softness:

    mSoftness = 0.0, Softness is 0%
    mSoftness = 0.5, Softness is 50%
    mSoftness = 1.0, Softness is 100%

  • mHard This is a simple boolean that usually relates to mSoftness. If mHard is YES, then mSoftness is typically 0.0. It determines if the brush is fully opaque or 50% overall. The two options:

    mHard = YES, Hard is yes
    mHard = NO, Hard is no

These parameters can obviously be used in conjunction with each other, in many different combinations.

Creating the brush tip image

Now let’s delve into how these parameters are implemented by dissecting the createShapeImage function, which creates the image the Canvas uses to stamp with. It is a fairly involved function, so we’ll take it piece by piece, starting with the declaration:

- (CGImageRef) createShapeImage

It doesn’t take any parameters, because it pulls from the member data, and returns a CGImageRef. The returned image is cached for the duration of the mouse tracking, then released.

The first thing we do is create a bitmap context to draw into:

// Create a bitmap context to hold our brush image
CGContextRef bitmapContext = [self createBitmapContext];

We do this by calling a member function, createBitmapContext, which I won’t detail here, because its uninteresting. It simply creates a bitmap context the size of the brush bounding box, and clears it to transparent.

Next we implement the part of the function that handles the mHard parameter. If mHard is yes, we want to render the brush with full opaqueness, otherwise, we want render the brush tip at 50% transparency. Since we’ll potentially be doing several drawing operations that should be treated as a whole, we need to group them using a transparency layer:

// If we're not going to have a hard edge, set the alpha to 50% (using a
//	transparency layer) so the brush strokes fade in and out more.
if ( !mHard )
	CGContextSetAlpha(bitmapContext, 0.5);
CGContextBeginTransparencyLayer(bitmapContext, nil);

mHard works because making the brush tip 50% transparent means the very edges of the line are no more than 50% opaque, which gives them a softer look.

Now that we’re inside the transparency layer, we want to start setting up the layer for drawing. The first thing to do is set the color, using mColor.

// I like a little color in my brushes
CGContextSetFillColorWithColor(bitmapContext, mColor);

That’s pretty self explanatory, but the next part isn’t. We want to handle the softness of the brush edges, which are specified in mSoftness as a percentage. The general idea is to “terrace” the shape at different transparency levels. So at the outer edges of the brush, we draw the shape at full size, but almost fully transparent. As we move towards the center of the brush, the shape should be drawn more and more opaque, at smaller and smaller sizes.

The mSoftness variable determines how soon we reach fully opaque as we draw from the outside in. At 0.0 mSoftness, we’re fully opaque at the outside radius. At 1.0 mSoftness everything but the very center pixel is somewhat transparent.

Since we’re working from the outside in, we know we’re going to start at the outside radius, but we need to compute at what radius the shape becomes fully opaque (after it’s fully opaque, it doesn’t make sense to keep drawing).

// The way we achieve "softness" on the edges of the brush is to draw
//	the shape full size with some transparency, then keep drawing the shape
//	at smaller sizes with the same transparency level. Thus, the center
//	builds up and is darker, while edges remain partially transparent.

// First, based on the softness setting, determine the radius of the fully
//	opaque pixels.
int innerRadius = (int)ceil(mSoftness * (0.5 - mRadius) + mRadius);
int outerRadius = (int)ceil(mRadius);
int i = 0;

Here innerRadius is the radius at which the brush is fully opaque. outerRadius is always the same as mRadius, but we cast it to an int so we can use it in a for loop. i is just the loop counter that I declared here because stupid C won’t let me declare it in the for loop initialization statement.

The last thing we do before we go into our loop to render the soft brush edges, is set the alpha. The nice thing is that the alpha channel builds up, so as you repeatedly draw over a transparent shape, it becomes more opaque. Since we’re working from the outside in, we only have to set the alpha once, outside the loop, and the brush will automatically become more opaque as we continue drawing.

// The alpha level is always proportial to the difference between the inner, opaque
//	radius and the outer, transparent radius.
float alphaStep = 1.0 / (outerRadius - innerRadius + 1);

// Since we're drawing shape on top of shape, we only need to set the alpha once
CGContextSetAlpha(bitmapContext, alphaStep);

Next is our edges loop, which also handles the case of mSoftness = 0.0.

for (i = outerRadius; i >= innerRadius; --i) {
	CGContextSaveGState(bitmapContext);

As you can see, we’re just working from the outside in.

Now that we’re inside the edges loop, we want to center and scale the context so the image shows up at the right location with the right size.

	// First, center the shape onto the context.
	CGContextTranslateCTM(bitmapContext, outerRadius - i, outerRadius - i);

	// Second, scale the the brush shape, such that each successive iteration
	//	is two pixels smaller in width and height than the previous iteration.
	float scale = (2.0 * (float)i) / (2.0 * (float)outerRadius);
	CGContextScaleCTM(bitmapContext, scale, scale);

The centering is pretty self explanatory. Since we’re shrinking the radius one pixel at a time, just move the origin in by the number of times we’ve been through the loop. Scaling works the same way, except we have to remember that we were dealing with the radius, so we need to double it to get the diameter.

Now that we have the current iteration of the edges loop centered and scaled, we just have to fill the shape specified in mShape.

	// Finally, actually add the path and fill it
	CGContextAddPath(bitmapContext, mShape);
	CGContextEOFillPath(bitmapContext);

The last final bit is to just restore the graphics state for the next loop iteration:

	CGContextRestoreGState(bitmapContext);
}

We’re done rendering the brush tip into the transparency layer, so it’s time to end the transparency layer and composite it back onto our bitmap context at the alpha level determined by mHard:

// We're done drawing, composite the tip onto the context using whatever
//	alpha we had set up before BeginTransparencyLayer.
CGContextEndTransparencyLayer(bitmapContext);

The rest of the function is housekeeping. We convert the bitmap context into a CGImageRef, free up the bitmap context, and return the image:

// Create the brush tip image from our bitmap context
CGImageRef image = CGBitmapContextCreateImage(bitmapContext);

// Free up the offscreen bitmap
[self disposeBitmapContext:bitmapContext];

return image;

Like it’s counterpart, disposeBitmapContext isn’t that interesting (it just frees up the bitmap context), so I’ll skip it here.

And that covers the creating of the brush’s image, which is the most interesting part of the Brush class. It is pretty straight forward, except for maybe the render of the “soft” edges.

Handling mouse events

The remainder of the Brush class just takes input from the user (via the CanvasView class) and translates it into primitives for the Canvas class. These enter through methods for mouse down, mouse dragged, and mouse up.

The first method that is invoked during user interaction is mouseDown:inView:onCanvas:. This function needs to initialize the tracking data, and then ask the Canvas to render the first stamp.

- (void) mouseDown:(NSEvent *)theEvent inView:(NSView *)view onCanvas:(Canvas *)canvas
{
	// Translate the event point location into a canvas point
	NSPoint currentPoint = [self canvasLocation:theEvent view:view];

	// Initialize all the tracking information. This includes creating an image
	//	of the brush tip
	mMask = [self createShapeImage];
	mLastPoint = currentPoint;
	mLeftOverDistance = 0.0;

	// Since this is a mouse down, we want to stamp the brush's image not matter
	//	what.
	[canvas stampMask:mMask at:currentPoint];

	// This isn't very efficient, but we need to tell the view to redraw. A better
	//	version would have the canvas itself to generate an invalidate for the view
	//	(since it knows exactly where the bits changed).
	[view setNeedsDisplay:YES];
}

The first thing we do in mouseDown:, and all the mouse functions, is convert the mouse location into something relative to the canvas. We use a helper function, canvasLocation:view:, to do this, but in a real system, the CanvasView would do the conversion for us, since we shouldn’t know details about how the Canvas is located in the CanvasView. However, I didn’t want to replicate most of the information in NSEvent just so it could be done that way.

Since we’re in the mouseDown:, we need to initialize the tracking data, which includes the brush tip image (calling createShapeImage, which we just covered), remembering the current point for the next time we’re called, and initializing the left over distance from the last time we asked the Canvas to render a line.

The mouseDown: is also unique in that we don’t ask the Canvas to render a line, but instead, render a single point. If you were paying attention during stampMask:from:to:leftOverDistance: function, you’ll note that it never stamps at the start point. So we have to do that manually now, which is also good because it gives the user immediate feedback as soon as the mouse goes down.

Finally, we have to tell the view to refresh itself. Here we just tell it to redraw the entire view, which isn’t very efficient. In a real system, the Canvas would determine the bounds that changed, inform the CanvasView in Canvas coordinates, the CanvasView would convert the Canvas coordinates to view coordinates, and invalidate that part of the view. i.e. The tool wouldn’t be involved in the invalidation loop.

After we kick off the tracking loop, we’ll start getting mouseDragged: messages. This function has a simple function: tell the Canvas to draw a line from the last point we got, to the current point.

- (void) mouseDragged:(NSEvent *)theEvent inView:(NSView *)view onCanvas:(Canvas *)canvas
{
	// Translate the event point location into a canvas point
	NSPoint currentPoint = [self canvasLocation:theEvent view:view];

	// Stamp the brush in a line, from the last mouse location to the current one
	[self stampStart:mLastPoint end:currentPoint inView:view onCanvas:canvas];

	// Remember the current point, so that next time we know where to start
	//	the line
	mLastPoint = currentPoint;
}

Like mouseDown: we convert the event point into a canvas point. Then we call a helper function on Brush that will tell the Canvas where to draw the line. Finally, we remember where the current point was.

The last function in the tracking loop is mouseUp: which will end it. It tells the Canvas the last line to draw, and cleans up all the tracking information.

- (void) mouseUp:(NSEvent *)theEvent inView:(NSView *)view onCanvas:(Canvas *)canvas
{
	// Translate the event point location into a canvas point
	NSPoint currentPoint = [self canvasLocation:theEvent view:view];

	// Stamp the brush in a line, from the last mouse location to the current one
	[self stampStart:mLastPoint end:currentPoint inView:view onCanvas:canvas];

	// This is a mouse up, so we are done tracking. Use this opportunity to clean
	//	up all the tracking information, including the brush tip image.
	CGImageRelease(mMask);
	mMask = nil;
	mLastPoint = NSZeroPoint;
	mLeftOverDistance = 0.0;
}

The first part of mouseUp: is identical to mouseDragged:, so I’ll skip the explanation. The last part simply does clean up: freeing the brush tip image, and resetting the last point and left over distance for the line rendering.

Helper functions for tracking

The only functions left for the tracking is a couple of helper functions that mouseDown:, mouseDragged:, and mouseUp: call.

The first one is canvasLocation:view: which we called earlier to convert an NSEvent mouse location into a point relative to the Canvas:

- (NSPoint) canvasLocation:(NSEvent *)theEvent view:(NSView *)view
{
	// Currently we assume that the NSView here is a CanvasView, which means
	//	that the view is not scaled or offset. i.e. There is a one to one
	//	correlation between the view coordinates and the canvas coordinates.
	NSPoint eventLocation = [theEvent locationInWindow];
	return [view convertPoint:eventLocation fromView:nil];
}

This function simply converts the mouse event location into a point relative to the view. It makes the assumption that the canvas is positioned at the origin of the view and is not scaled. As stated before, in a real system this would be handled in the CanvasView, and not here.

Finally, the last helper function used in tracking is stampStart:end:inView:onCanvas:, which simply tells the Canvas where to draw a line.

- (void) stampStart:(NSPoint)startPoint end:(NSPoint)endPoint inView:(NSView *)view onCanvas:(Canvas *)canvas
{
	// We need to ask the canvas to draw a line using the brush. Keep track
	//	of the distance left over that we didn't draw this time (so we draw
	//	it next time).
	mLeftOverDistance = [canvas stampMask:mMask from:startPoint to:endPoint leftOverDistance:mLeftOverDistance];

	// This isn't very efficient, but we need to tell the view to redraw. A better
	//	version would have the canvas itself to generate an invalidate for the view
	//	(since it knows exactly where the bits changed).
	[view setNeedsDisplay:YES];
}

We wrap the call to the Canvas up here for simplification reasons. We pass in and maintain the mLeftOverDistance member variable, as well as the brush image in mMask. Also, we do our inefficient view invalidation so that the line shows up on screen.

That’s the complete Brush class. As review, it has two main functions:

  1. Create the brush tip image that it will pass to the Canvas to use to stamp with.
  2. Tracking the user’s mouse, and tell the Canvas where to draw lines.

Conclusion

Hopefully this has been a fun and interesting tutorial for you. If not, it turns out I still enjoyed it anyway.

There’s still lots of things that could be improved on in the sample code, which include, but are not limited to:

  • Implementing texture support
  • Modifying the code to handle non-square brushes
  • Implementing some sort of UI to configure the brush
  • Implementing some sort of UI feedback for the brush size and shape
  • Implementing tablet support, such as pressure, tilt, and rotation
  • Handling velocity

So if you’re looking for things to play around with in the sample code, there are some ideas.

Download Sample Code

Late Night Cocoa: Image Unit Plugins

It’s always kind of a horror to hear yourself speak, but Scotty has done a great job of making me sound like I walk upright, at least on weekends, on the latest episode of Late Night Cocoa. I was hoping to come out sounding like James Earl Jones, but I suppose there are limits to modern technology and podcasting ethics. Maybe the fine folks at Rogue Amoeba will come out with something, perhaps an amoeba shaped plush toy.

Anyway, if you’d like to hear someone (me, specifically) drone on about Core Image Filters and Image Unit Plugins, you should tune into Late Night Cocoa: Core Image Filters, if its not already in your iTunes feed. It has lots of detailed information on Core Image filters, spoken in a high nasally voice.

Just think of it as James Earl Jones’ voice in drag.

Useless code

When contracting, I get to see lots of code. Some of it is good, some of it bad, and some just leaves me scratching my head.

For example, I came across this today:

//ASSERT(1);

Apparently things had gotten so bad for this programmer that he needed to make sure 1 still evaluated to true. I’ve had days like that. Better yet, when done, he just commented the line out, helpfully allowing future developers to quickly make use of his wisdom.

Such a philanthropist.

How do you pick a product idea?

Ever since I got here at Order N I’ve been wanting to develop our own product. It’s been on the back burner for quite a while (I’ve been with the company for over a year and a half now), slowly percolating. We’ve managed to generate a few ideas (127, to be exact) as far as products go, but we haven’t done anything with them.

At this point, I think we’ve got enough ideas, and we just need to pick one and move forward with it. The question is: which one?

I wrote up a process document on how to pick any idea (which is below), but I’m wondering if its the right way to do things. So I have a question to those of you have built your own product(s) (or are in the process of doing so): How did you decide what to build?

Did you simply build what you wanted to? Did you do research and find a product gap and fill it? Did you try to find the idea that you thought would generate the most money or the most users? Did an idea just hit you one day and you decided you had to make it?

My “process” for creating and picking an idea follows:

The purpose of this document is the establish a simple, lightweight process for coming up with and evaluating product ideas. The process should result in at least one idea that we can turn into a viable (read: profitable) product. The process is flexible and can be changed as circumstances change or as better ideas are introduced. The process evaluates ideas based on business factors, not engineering factors.

1. The first step in the product idea process is generating ideas (i.e. brainstorming). At this point in the process the ideas are vague and not well defined. The purpose of this stage is to generate as many ideas as possible, without evaluating them. The hope is that enough ideas are generated that a few of them are viable both engineering and business-wise.

Ideally, at the end of this step, we should have at least a few hundred ideas to choose from.

2. Second, after generating all those ideas, the next step is to whittle them down to a manageable number so we can do research on them. This will probably be between 10 and 20 ideas. By applying some simple criteria, we should be able to arrive at the top ideas we might be able to pursue. Ideally these criteria do not require research, but can be answered easily and quickly.

Criteria:

- What user problem/pain does this solve? If it doesn’t solve a problem, no one will buy it.

- How is the user going to pay for it? Or how does the product generate money? If a cool idea can’t generate money, its not worth it. A lot of Web 2.0 apps fall into this (like digg, YouTube, etc). They solve problems, but they don’t make money.

- What is the potential customer base? i.e. Is it consumer, professional, or developer level product? This will help rate the ideas – a consumer product is usually more valuable than a developer product since there’s potentially a larger customer base.

- Without architecting or engineering the product, is the product even technically feasible? If we’re trying to make cold fusion work, we should probably pass on that for now.

These criteria probably will not eliminate all but 10 or 20 ideas, but they should help us rank them and pick out the best 10 or 20 ideas. Some ideas might have to be fleshed out a bit more, but hopefully even vague ideas can be evaluated at this step in the process.

3. Next we need to research the top 10 or 20 ideas. This means fleshing them out a bit more so we can make more critical decisions about them. The research is targeted at finding out how much money the product might bring in, how likely we are to attract customers, and what building the product might cost.

What we need to know:

- How big is the potential customer base? This is an extension of what kind of product is it: consumer, professional or developer. Do a lot of people have the problem this idea is trying to solve, or is it a niche problem?

- What can we charge for the product? What is the competition charging? Not trying to determine final pricing here, but what is the range we could expect.

- How will we sustain income with the product? Upgrades, subscriptions, ads?

- Is there any competition? If so, who is the leader? What makes the leader, the leader? Can another product be sustained in this environment?

- What are the core/basic features in the product? We don’t need or want a feature spec here, just a general idea of what we’re providing. This should help with cost of building as well as what we can charge.

- What will set us apart from the competition? i.e. Do we think we can actually capture part of the market?

- What are the engineering costs in regards to time? i.e. how many engineers for how long? We don’t need a real number, just general estimates so we can compare it against the other ideas.

- What is the required infrastructure to make this work? This would obviously be bigger for web apps which need a large number of servers. Don’t forget about add ins to do try-before-you-buy or other demo schemes.

- What kind of marketing might we need to make the product a success? Mainly we want to know how expensive it will be to market the product.

- Are there legal or other expenses (like facilities or sales people or development software) that are required?

4. Finally, we need to evaluate the product ideas based on our research. Knowing how big our customer base is and how much we can charge will give us a ballpark of how much money the product could potentially bring in. The competitive analysis and feature ideas will give us an idea of how much of that money we might be able to get. The engineering cost estimates and required infrastructure costs will give us an estimate of the total cost to build the product.

So the basic “value” of the product idea is:

(Potential money in the market) * (Part of the market we get) – (Total costs of building product) = Profit

That’s real scientific stuff. Please don’t take it too seriously.

We’re not going to get hard and fast numbers out of this step, but it should give us a vague idea which idea is more valuable, business-wise, than the others. At the end of this step we should have at least one (if not more) idea that we can then take on to the product development process.

Once again, this is a light weight process that can (and probably will) change as we learn things. If you have ideas, suggestions, or comments about how to make this better, please let me know.

As you can tell, my process focuses on what product will bring in the most money. While money is good, I don’t want to build a product that I won’t enjoy working on.

What are your thoughts?

levitra 125 mg
cheapest prices on generic viagra
buy free viagra viagra
buy viagra onli
buy viagra online and get prescription
cheap overnight viagra
huricane ike home repair wanted
buy online pill viagra
cheap viagra online uk
50mg cialis
buy cheap deal viagra viagra viagra
cheap cialis canada
36 hour cialis
mobile home roof repair
cheap levitra online
20 mg cialis
home study computer repair course dvd
buy cheap generic viagra
buy viagra in great britain
buy viagra without prescription
buy viagra in london
buy cheap viagra uk
cheap pill viagra
viagra and coupon
generic viagra 20mg pills erections
voagra online without prescription
viagra 50 mg
viagra and cialis and
bruces home repair salem or
cheap online generic viagra
viagra 100mg price
buy viagra in spain
cialis 5m tablets
cheapest price for generic viagra
mobile home repair rochester wa
buy viagra softtabs
cheapest price viagra
viagra buy it online now
rx cialis 100mg
buy low price viagra
cheapest place to buy viagra
buy cheap online uk viagra
home foundation repair
100mg viagra
buy pill viagra
buy viagra online
voagra online without prescription
buy viagra in canada
generic viagra 100mg
cheap online viagra viagra
buy line viagra where
federal grants for home repair
cheap prescription viagra
cheapest generic price viagra
10 generic levitra for 19.95
buy prescription viagra
levitra online prescription
buy levitra 50mg
cialis brand cialis 100mg
buy cialis online 50mg
in home tv repair canton nc
buy viagra in toronto
viagra by mail canada
home repair tax deductable
100 mg cialis us pharmacy
viagra brand viagra 100mg
mobile home floor repair
buy viagra online
100mg levitra
levitra 25 mg order
viagra 20 mg
buy viagra next day delivery
cheapest uk supplier viagra
viagra and cialis cheap
cialis 10mg 20mg
cheapest 4 quantity of viagra
buy levitra online
buy cialis online viagra
250 mg viagra
home window repair in sc
buy generic viagra viagra
buy cheap generic viagra online
cheap cialis canada
cheapest price on viagra
cheap viagra canada
viagra by the pill
buy keyword online viagra
cheapest prescription viagra
buy viagra on-line
50 mg viagra retail price
buy online drug viagra pharmacy
cheap 25mg levitra
viagra buy ionline
cialis 100mg price
levitra without prescription
home repair denver coloradao
buy discount viagra
buy cheap online prescription viagra
viagra 25 mg
buy now online viagra
levitra 10mg 20mg
buy cost low viagra
purchase levitra online
buy viagra onlines
buy viagra in australia
ron hazelton home repair
buy cheap online viagra viagra
cheap online pill viagra
100 mg levitra us pharmacy
cheap phizer viagra
bruces home repair
viagra by phone
buy online online viagra viagra
buy cheap viagra online u
cheap levitra canada
cialis online prescription
cheap levitra online
buy locally viagra
buy cheap cialis generic levitra viagra
buy cialis internet
cheap viagra uks
sears home repair
buy viagra generic
50 mg cialis retail price
order 50mg levitra
viagra 50mg
cialis online
cialis 20
cheapest price for viagra
buy site viagra
discount drugs levitra 100mg
cheapest brand viagra
buy levitra online 50mg
home repair replacing circuit breakers
buy viagra low cost
buy prescription vaniqa viagra
discount drugs cialis 100mg
buy cheapest viagra
buy discount propecia
home computer repair
buy cheapest online viagra
levitra 50mg
viagra buy online
home cd repair
cialis 25 mg
viagra buy viagra
buy in online uk viagra
home remedies to repair scratched cds
buy discount levitra
cheapest place buy viagra online
buy deal deal price viagra
buy online prescription viagra
viagra by mail order
buy viagra pill
viagra buy uk
250 mg levitra
buy deal herbal viagra viagra
buy pfizer viagra
buy cheap viagra online now uk
buy viagra internet
cheapest line viagra
buy viagra professional
buy cialis without prescription
buy viagra in uk
cialis 10 mg
viagra and cialas
good review sites for home repair
home repair vancouver
cheapest generic viagra and canada
cialis no prescription
10 generic cialis for 19.95
rx viagra 100mg
levitra 25 mg
buy viagra in malaysia
buy online levitra cialis viagra
vista home premium repair
cheap cialis canada
buy discount levitra
cheap viagra uk
buy online sale viagra
home appliance do it yourself repair
viagra by money order
levitra no prescription
cialis 20mg
cialis 20mg reviews
50 mg cialis
cialis reviews
buy cialis no prescription
buy generic viagra online
buy viagra removethis
buy levitra online viagra
cialis 24
buy cheap deal pill viagra
levitra 50 mg
weed eater home repair
mobile home repair remodeling
buy kamagra viagra
cialis 100mg
cialis 20 mg
cheap order site viagra
generic cialis 100mg
cialis 50 mg
buy viagra london
buy cheap discount levitra
cheap discount levitra
viagra no prescription
viagra 25 mg order
buy in online usa viagra
buy sale viagra
10mg cialis
buy viagra pills
viagra brand
buy viagra 50mg
buy viagra locally
cialis no prescription
cheap sale viagra
mobile home skirting repair
cialis 1
cheap prescription viagra without
buy real viagra online pharmacy
cheap viagra st
cialis 32
cheap online softtabs viagra
cheaper viagra levitra cyalis
buy viagra in mexico
buy online uk viagra
buy cheap viagra
cialis 125 mg
buy cialis viagra
viagra without prescription
buy online purchase viagra
cialis 50mg online
order 50mg cialis
buy lady uk viagra
50 mg levitra retail price
cheap site viagra
home repair grants
buy viagra usa
buy viagra uk
levitra 50mg online
36 hour levitra
buy online prescription viagra without
buy 100 mg viagra
cheapest generic viagra and cialis
buy viagra line
cheap online purchase viagra
buy generic viagra buy
cialis 2005
cheapest in uk viagra
buy viagra online alternative viagra
viagra buy oonline
scottsdale home repair
generic cialis wholesale 100mg
buy generic viagra
mobile home repair
buy cheap cialis
cheap viagra online prescription
buy cialis no prescription
buy now viagra
buy viagra sale
home repair grant
buy viagra in the uk
cialis black 800mg
buy pharmacy pill viagra
home electrical repair
order levitra
do it yourself home repair
cheap online sales viagra
buy no online prescription viagra
viagra 100mg
buy real viagra online
viagra by mail
buy viagra in bangkok
50 mg viagra
viagra buy in uk online
home repair frozen pipes
viagra buy viagra online
cheap pharmaceutical viagra
cheapest price for viagra and cialis
buy cheap cialis
buy viagra no prescription
canada pharmacy discounted levitra 100
buy real viagra
50 mg levitra
buy viagra in new zealand
home appliance repair
buy viagra london
buy cheap generic online viagra
buy viagra no prescription
36 hour viagra
mobile home repair parts
buy kamagra viagra india
buy price viagra
buy pill price price viagra
cialis 10
buy now levitra
cheap online viagra
buy cheapest viagra online
canada pharmacy discounted viagra 100
cheapest generic substitute viagra
buy later now pay viagra
cheap online price price viagra
buy say viagra
100 mg viagra us pharmacy
20mg cialis
cialis without prescription
order levitra online
cheapest place to buy viagra online
viagra by overnight delivery
buy cheap deal online viagra viagra
generic levitra wholesale 100mg
buy line viagra
buy viagra phuket
levitra 100mg
cheap price viagra
cialis 10
cheap 25mg viagra
cialis 25 mg order
viagra online prescription
generic cialis
cialis black
cheap online order viagra
buy online order viagra
viagra brazil
levitra 20 mg
buy online sale viagra viagra
viagra buy now pay later
amana refrigerator home repair
buy cialis now
registar repair windows xp home
cheap pill pill sale viagra
generic levitra 20mg pills erections
cialis 36 hours
cheapest online viagra
viagra buy australia
buy viagra online 50mg
buy cheap viagra online uk
cheapest cialis
buy viagra inte
buy prescription viagra without
cheapest generic viagra
levitra 100mg price
viagra 125 mg
viagra buy generic
home repair
buy viagra toronto
home repair handymen in fernley nevada
cialis without prescription
cialis 2005
cheap viagra without prescription
canada pharmacy discounted cialis 100
buy in uk viagra
buy pharmaceutical viagra
30mg cialis
discount drugs viagra 100mg
cialis 2.5
buy real viagra pharmacy online
buy cheap viagra on
cialis 20mg
home repair maintenance
home window repair
buy cheap viagra in uk
250 mg cialis
buy viagra in united kingdom
cialis 30mg
buy generic viagra pharmacy online
buy cheap viagra online now
cialis 20
tamiflu
100mg cialis
buy online price viagra
buy online online pill viagra viagra
10mg levitra
buy viagra soft
buy levitra no prescription
home repair plumbing
american pacific home repair
cheapest regalis viagra
home repair business
buy in spain viagra
buy free online sale viagra viagra
buy cheapest online place viagra
cialis 20 mg
buy generic no online prescription viagra
buy viagra line
10 generic viagra for 19.95
cialis 5mg
buy internet viagra
viagra by mail catalog
buy free viagra on internet
order 50mg viagra
buy cialis 50mg
cheapest site viagra
buy discount cialis
buy cheap viagra on the net
cheap soft tab viagra
cheap 25mg cialis
buy cialis online now
viagra and cialis
cialis 50mg
voagra online without prescription
cialis 10mg
usa cialis
buy cheap levitra
5mg cialis
viagra 10mg 20mg
buy viagra online at cheap price
cheapest cheap viagra
cheap viagra online order viagra now
home entertainment repair tips
40 grams of cialis
buy levitra canada