How to implement sharpen and blur tools

andy on Sep 17th 2009

The sharpen and blur tools are simple bitmap tools. They allow you to blur or sharpen parts of an image using a brush metaphor. In addition to being able to control the strength of the sharpen or blur, these tools also typically allow you to control the blend mode when applying the effect.

The sharpen tool has the following effect on this tweed pattern:

Tweed pattern with sharpen applied

Notice the stripe through the middle where the edges are more strongly defined.

The blur tool has the opposite effect of sharpen on the tweed pattern:

Tweed pattern with blur applied

Implementation Overview

Like the sponge tool, the sharpen and blur tools are subclasses of the FilterBrush class introduced in the dodge and burn tutorial. However, unlike sponge, they do require a slight modification to the GenericBrush class to work.

Since almost all of the code in this tutorial has been covered before, I’ll just highlight the single change to the GenericBrush class, and the new Sharpen and Blur classes. If you want to see everything, download the code.

Changing GenericBrush

If you recall, at the very beginning of this article I mentioned that sharpen and blur tools typically allow the user to modify the blend mode. This is the blend mode used when stamping the filtered image back on to the original image. GenericBrush contains the code that handles the stamping, so it will need to be modified.

We change the render:at: method for GenericBrush to be:

- (void) render:(Canvas *)canvas at:(NSPoint)point
{
	CGContextRef canvasContext = [canvas context];
	CGContextSaveGState(canvasContext);

	CGPoint bottomLeft = CGPointMake( point.x - CGImageGetWidth(mMask) * 0.5, point.y - CGImageGetHeight(mMask) * 0.5 );

	// Our brush has a shape and soft edges. These are replicated by using our
	//	brush tip as a mask to clip to. No matter what we render after this,
	//	it will be in the proper shape of our brush.
	CGRect brushBounds = CGRectMake(bottomLeft.x, bottomLeft.y, CGImageGetWidth(mMask), CGImageGetHeight(mMask));

	CGContextClipToMask(canvasContext, brushBounds, mMask);
	CGContextSetBlendMode(canvasContext, [self blendMode]);
	[self renderInCanvas:canvas bounds:brushBounds at:point];

	CGContextRestoreGState(canvasContext);
}

The only change made is the call to CGContextSetBlendMode that allows brush subclasses to determine the blend mode to use. The default implementation is simply:

- (CGBlendMode) blendMode
{
	return kCGBlendModeNormal;
}

That’s it for GenericBrush.

Sharpen Tool

The sharpen tool has just two parameters: mMode, which is the blend mode, and mStrength which determines how strong the sharpen effect is applied. They are initialized in init like so:

- (id) init
{
	self = [super init];
	if ( self != nil ) {
		// Set the default values for our parameters
		mMode = kCGBlendModeDarken;
		mStrength = 1.0;
	}
	return self;
}

Here are some examples of the sharpen tool parameters:

mMode mStrength Result
kCGBlendModeNormal 0.5 Tweed with sharpen, normal blend mode, 50%
kCGBlendModeNormal 1.0 Tweed with sharpen, normal blend mode, 100%
kCGBlendModeDarken 1.0 Tweed with sharpen, darken blend mode, 100%
kCGBlendModeLighten 1.0 Tweed with sharpen, lighten blend mode, 100%

The blend modes typically offered for a sharpen tool are kCGBlendModeNormal, kCGBlendModeDarken, kCGBlendModeLighten, kCGBlendModeHue, kCGBlendModeSaturation, kCGBlendModeColor, and kCGBlendModeLuminosity. In the examples above, kCGBlendModeDarken causes the sharpening to only affect light pixels and kCGBlendModeLighten only affects dark pixels.

The heart of the sharpen tool is the filter it creates. The code for that is:

- (CIFilter *) filter
{
	// We need to create and configure our filter here
	CIFilter * filter = [CIFilter filterWithName:@"CIUnsharpMask"];
	[filter setDefaults];
	[filter setValue:[NSNumber numberWithFloat:mStrength * 100.0] forKey:@"inputRadius"];
	return filter;
}

You’ll notice that I’m using the unsharp mask filter to do the sharpening. I should point out that most image editors don’t use this filter to do the sharpening. Instead, they seem to use a convolution filter to do the sharpening. However, the convolution filter means it is easy for the user to overdue the effect. Unsharp mask does not have that problem, plus unsharp mask is provided by the system.

The final bit of the sharpen tool is returning the correct blend mode:

- (CGBlendMode) blendMode
{
	return mMode;
}

Blur Tool

The blur tool is very similar to the sharpen tool. It even takes the same parameters. The only real difference is the filter that it creates, so that’s what I’ll present first:

- (CIFilter *) filter
{
	// We need to create and configure our filter here.
	CIFilter * filter = [CIFilter filterWithName:@"CIBoxBlur"];
	[filter setDefaults];
	[filter setValue:[NSNumber numberWithFloat:mStrength * 7.0] forKey:@"inputRadius"];

	return filter;
}

Here I use the built in CIBoxBlur. mStrength ranges from 0 to 1, inclusive. I set the coefficient of 7 simply by trial and error until I got something that “looked right.”

Here are some examples of the blur tool:

mMode mStrength Result
kCGBlendModeNormal 0.5 Tweed with 50% blur, normal blend mode
kCGBlendModeNormal 1.0 Tweed with 100% blur, normal blend mode
kCGBlendModeDarken 1.0 Tweed with 100% blur, darken blend mode
kCGBlendModeLighten 1.0 Tweed with 100% blur, lighten blend mode

Conclusion

Sharpen and blur are the last of the common filter brushes. Enjoy, and download the sample code.

Filed in Core Image, Macintosh, Programming | Comments Off

How to implement a sponge tool

andy on Sep 16th 2009

There aren’t many bitmap tools that I look at and think “hmmm… I could probably crank that out in about half an hour.” But because of some previous work I had done, it turned out I wasn’t too far from the mark when it comes to the sponge tool.

Overview

In concept, the sponge tool is a simple tool. It either increases or decreases the saturation of an image. Saturation describes how intense a given color is. 0% saturation means the color is gray. In the implementation that I will show, the sponge tool will behave like a brush.

Thus, if I have a simple red square with about 50% saturation, the sponge tool in the saturation mode affects it like so:

Red square with saturation applied

The desaturation mode has the opposite effect on the same image:

Red square with desaturation applied

In addition to the saturate/desaturate mode, the sponge tool has a “flow” parameter, which determines how much to saturate or desaturate the image.

Implementation Overview

The sponge tool is simply a subclass of the FilterBrush class, which I created for the dodge and burn tutorial. In fact the only difference between this tutorial and that one is the new subclass Sponge. For that reason I’ll only be covering the Sponge class in this tutorial. For an explanation as to how the rest of the code works, please refer back to the dodge and burn tutorial.

As always, I’ve provided sample code for this article.

Sponge

Parameters

The sponge tool has two parameters which are initialized in the init method:

- (id) init
{
	self = [super init];
	if ( self != nil ) {
		// Set the default values for our parameters
		mFlow = 0.5;
		mMode = kSpongeMode_Desaturate;
	}
	return self;
}

The two parameters for the sponge tool are mFlow and mMode.

  • mFlow Flow determines how strong the saturation or desaturation effect is applied to the image. It ranges from 0.0 to 1.0, where 0.0 means the effect isn’t applied, and 1.0 is where the effect is at its strongest.

    Sponge in saturate mode, varying flow:

    mFlow Result
    0.25 Red square with 25% saturation applied
    0.5 Red square with 50% saturation applied
    1.0 Red square with 100% saturation applied
  • mMode Mode determines if the sponge saturates or desaturates the image. It has only two settings: saturate or desaturate. Saturate increases the color’s saturation, while desaturate decreases saturation.

    Sponge examples:

    mMode Result
    kSpongeMode_Saturate Red square with saturation applied
    kSpongeMode_Desaturate Red square with desaturation applied

In the examples I used a red rectangle with about a 50% saturation. That way, both saturation and desaturation effects will show up on it.

Creating the filter

If you recall from the dodge and burn tutorial, the only real responsibility of a FilterBrush subclass is to create a filter that will be applied to each brush stamp.

- (CIFilter *) filter
{
	// We need to create and configure our filter here. CIColorControls has controls
	//	for saturation which is what we care about.
	CIFilter * filter = [CIFilter filterWithName:@"CIColorControls"];
	[filter setDefaults];
	if ( mMode == kSpongeMode_Saturate )
		[filter setValue:[NSNumber numberWithFloat:1 + mFlow] forKey:@"inputSaturation"];
	else
		[filter setValue:[NSNumber numberWithFloat:1 - mFlow] forKey:@"inputSaturation"];

	return filter;
}

Unlike the dodge and burn tools, I don’t use a custom filter. Instead, I use the system provided CIColorControls, which has a parameter for saturation. The saturation parameter has a range of 0 to 2, where 1 is the identity value. Thus, if I’m trying to saturate the image, I add 1.0 to mFlow, and if I’m trying to desaturate the image, I subtract mFlow from 1.0.

That’s it. The rest of the heavy lifting is done by the framework I created in the dodge and burn tutorial.

Conclusion

The main thing this tutorial demonstrates to me is that the time spent building a framework for filter brushes paid off. It should help in the future if I decide to tackle the blur and sharpen brushes as well.

Hopefully you found this post enlightening. Don’t forget to download the source code.

Filed in Core Image, Macintosh, Programming | Comments Off

[UIImage imageNamed:] is a memory leak, but only on 2.x

andy on Sep 14th 2009

Update: I was contacted by an Apple engineer saying this bug was fixed in 3.0, and asked if I could reproduce it there. I tried my sample project again, and I was unable to reproduce it on 3.0. This problem only seems to affect 2.x devices.

Not too long ago I was profiling an iPhone app that I had written for a client. Their testers had found that using a certain feature for long enough ended up causing the app to be ejected because of a low memory situation.

After some quality, yet painful, time with Instruments I discovered I was never deallocating image memory in the form of CGImageRef and its internal drawing cache. (It turns out that if you draw a CGImageRef while on a thread other than the main thread, it creates a cache of the bitmap data. However, this wasn’t my problem.)

At first I thought I simply forgot to release the UIImages somewhere. I put in some logging code and was able to confirm that I was releasing the UIImages the correct number of times, in the right places. But it appeared that someone had called retain on it an extra time.

That’s when I happened to notice that only the UIImages I had constructed with imageNamed: were failing to be released. I suspected that they were being retained in a global cache. The only problem with this cache is not released in low memory situations. Even when my app receives a low memory notification and I free up all the memory I can, the UIImage global cache just sits there clutching it’s unused UIImages to its chest, muttering.

Unfortunately for me, some of the UIImages I was loading were large, and I was expecting them to be deallocated when I released them. As the feature was used, different UIImages were loaded with imageNamed: and never released until the phone ran out of memory and my app was ejected.

The solution was to write a replacement method for imageNamed: that didn’t cache the UIImage. Here’s what it looks like:

@implementation UIImage (OrderNDev)

+ (id) imageNamedNoCache:(NSString *)name
{
	NSString *basename = [name stringByDeletingPathExtension];
	NSString *extension = [name pathExtension];
	NSString *path = [[NSBundle mainBundle] pathForResource:basename ofType:extension];
	return [[[UIImage alloc] initWithContentsOfFile:path] autorelease];
}

@end

The moral of the story is imageNamed: is only for use with small images that are used constantly throughout the app’s lifetime. Don’t use imageNamed: if you want that memory back on a 2.x device. Everything will work fine on a 3.x device though.

Filed in Programming, iPhone | One response so far

iPhone specific review sites

andy on Dec 20th 2008

I’m starting to think about marketing, PR, and what not for my iPhone game, so I’m trying to come up with a list of iPhone specific sites that might be appropriate targets for press releases and/or advertising. Since I have a game, game specific iPhone sites are even better.

It also occurred to me that other developers might be gathering this information, so I thought I’d post what I’ve found so far here. I got the idea from MobileOrchard.

General iPhone Sites

  1. AppCraver
  2. 148Apps
  3. App afari
  4. Apple iPhone Apps
  5. iPhone Application List
  6. NativeiPhoneApps
  7. iPhone Apps UK
  8. apptism
  9. What’s on iPhone
  10. iPhone Alley
  11. got apps?
  12. iPhone Footprint
  13. Zym Blog
  14. RazorianFly
  15. iGames
  16. touchtip
  17. iPhone Journal
  18. iPhone in Canada

iPhone Game Specific Sites

  1. touchArcade
  2. Pocket Gamer
  3. TiltGamer
  4. Finger Gaming
  5. Slide to Play
  6. IGN Wireless
  7. iPhone Games Network

Note that these sites do reviews, sell advertising, and/or have a place to submit an app for listing. I’m not including sites that do general iPhone reporting, but wouldn’t be appropriate for PR or advertising.

If you know of others, please feel free to leave links to them in the comments.

Filed in iPhone | 12 responses so far

Experiences with a Sound Designer

andy on Dec 13th 2008

The last time I spoke of my upcoming iPhone game, I was soliciting recommendations for graphics and sound designers. Since then I’ve hired a sound designer and had the sounds completed. I had originally intended to write about my experiences with sound and graphics designers in one article, but I actually just hired a graphics designer, so my experiences with him will have to wait until next time.

This is a description of my experiences with finding and hiring a sound designer to create royalty free, custom sound effects for my game. My experience with hiring and working with a sound designer was quite pleasant. The entire process was completed exactly one week after I first started contacting designers.

Preparation

Before I started contacting designers I sat down to figure out what exactly I needed from the sound designer, and what information he or she would probably need from me. I’ve never worked with sound designers before, so I had to make some educated guesses. In the end, my guesses seemed to be at least adequate. I wrote down everything I thought the designer would need in a specification document.

The specification document I create was pretty simple; it had two sections: an introduction and a description of the sounds. In the introduction I gave a brief overview of the game, how the sounds were going to be used, the “theme” I wanted, and a total count of sounds to be created. I managed to fit this into five sentences, on the assumption that the designer wanted to know about the sounds, and not my life story, fascinating as it is.

For each of the sounds, I created a description for it. I gave each sound a short, unique name so they were easy to refer to. I then described what the sound was, for real world sounds (e.g. card placed on a table), or what I wanted it to sound like, for abstract sounds (e.g. a sharp, clicking sound). I also tried to give the context in which the sound will be played (e.g. when a button is pushed). Finally, I stated the length I wanted the sound to be. Most of mine were only one or two seconds in duration.

Selecting the contestants

With the sound specs in hand, I was ready to start contacting designers. The only problem was that I didn’t know any, nor had I heard of anyone recommending any. Being put in such a desperate situation, I did what any engineer would do — I googled for some, and blogged about it. In the end, I contacted three different sound designers:

I originally found about six companies that I thought could possibly do the work, but I whittled down the choices to these by using the following criteria:

  1. Did it look like the designer had experience creating sound effects for games? Since that was what I wanted, it made sense to see if their portfolio reflected that.
  2. Could I navigate the designer’s site well enough to actually find a way to contact them? I didn’t originally intend for this be criteria, but it ended up being one. Advice for sound designers: hire a good web designer.

For each of the three designers I selected, I sent out an introductory email. In the email, I briefly described the project and asked if they were available and interested. I mentioned that I had a spec and would like a rough estimate before work began. In retrospect, I should have probably attached the sound specification to the introductory email, because that was the first thing everyone who was interested ask for.

Of the three designers I contacted, SoundRangers and IndieSFX replied to me within 24 hours. Sound for Games never got back to me. SoundRangers and IndieSFX charge by the sound, and offer both stock and custom sounds. For stock sounds, prices range from around $1 to $5 per sound, although IndieSFX only sells them in packs. For custom sounds, prices range from $10 to $40 per sound. None of the stock sounds were quite what I wanted, so I elected to go for all custom sounds.

Between SoundRangers and IndieSFX, I chose IndieSFX. Both seemed to be comparable in quality, but IndieSFX had better prices.

Creating the sounds

Once I had signed a contract for IndieSFX to create the sounds, I began working with Mark to do just that. Based on my specification, Mark created a first draft of the sounds by the next day. Some sounds had multiple variations for me to pick from, while others I was just given one choice to refine.

Mark and I went through about seven or eight different variations of the sounds before we settled on something. In general, I got a new revision of the sounds every day, which was highly motivating.

Along the way I learned a couple of things about sound. First, there’s the concept of “wet” versus “dry” sounds. Wet sounds have more echo and reverb and, at least in my case, sounded better. Dry sounds are shorter, which can be good if you have a tight time interval in which to play a sound. Second, I found out that playing the same sound several times in a row makes it sound like a typewriter or machine gun. Fortunately Mark knew how to fix this: create several variations of the sound and randomly pick a different one to play each time.

That leads me to something I learned about the process: always try out the sounds in the game. Sounds that sound great by themselves may not sound good in context, or when they’re interacting with other sounds. I unfortunately approved a couple of sounds that didn’t actually work well in the game. Fortunately Mark was nice and let me make changes even after I initially said they were OK.

The other thing I learned was that I needed to be as specific as possible when I wanted changes. It was easy to verbalize emotional reactions to sounds (I like this, I don’t like this, etc), but hard to spell out specifically how I wanted something changed, so Mark could actually do it. It turns out sound designers aren’t mind readers. Who knew?

After about a week of revisions, I had sound effects I was happy with. I paid IndieSFX, added their name to the about box, and checked the sounds into source control.

Using the sounds

During the creating process, I was taking the sounds Mark was sending to me and integrating them into my iPhone game. Since Mark was sending me uncompressed WAV files, I had to convert them before I could use them. Fortunately, Apple provides a nifty little command line tool to do just that: afconvert.

At first I was keeping the sounds uncompressed, for quality reasons, so I used afconvert like this:

/usr/bin/afconvert -f caff -d LEI16 <input WAV file> <output CAF file>

Unfortunately, high quality, uncompressed sounds are huge and this app is going to be distributed over the internet. So I switched to a compressed format, using this command line:

/usr/bin/afconvert -f caff -d 'ima4' <input WAV file> <output CAF file>

This gave me a 50% savings in size, with no discernible difference in quality. I chose these formats because I needed the sounds to be able to play simultaneously, and these are the two formats that the documentation say will allow that.

Lifting the veil

Hopefully this article has made the process of working with a sound designer more transparent. When I started this process, I had no idea what to expect — how much it would cost or how the process even worked. But I was quite pleasantly surprised. Getting custom sound effects created for an indie game is quite reasonable, both in time and money.

Filed in iPhone | 4 responses so far

Bad Behavior has blocked 1068 access attempts in the last 7 days.