Archive for the 'Programming' Category

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

Don’t lose your keys

andy on Oct 16th 2008

There are many difficult parts to creating and distributing an iPhone application. Don’t let the loss of an important file be one of them.

A client of mine recently had a scare when he (temporarily) lost his private key. Allow me to explain: for an iPhone application to be distributed, it has to be signed by a cryptographic key created by the developer. The signing is done by the private key, and verifying that the application is signed is done by the public key.

There are couple of things to note about the private key:

  1. The private key never leaves the machine it was created on. Ever.
  2. No one, including Apple, can regenerate your private key, if something were to happen to it.

While it is true that Apple has a copy of the developer’s certificate, they only have the public key, not the private. This makes sense, because whoever has your private key can distribute applications under your name. Thus, it’s not something you want to be storing just anywhere.

Losing your private key has a couple of implications, since it cannot be recovered and because developers can only have one distribution certificate at a time. This means the old certificate must be revoked before a new one can be created, which will caused all applications signed with the old certificate to die (Edit: I’ve been told by someone who revoked their certificate that it did not effect already distributed apps).

If you lose your private key your choice is to either never update your application ever again, or kill all existing copies of it.

After you upload a new version signed with the new certificate, your old customers can get the application, but only after paying for it again. Not a huge deal if your application is free, but if it’s not your customers will probably be upset.

All of this to say: you should be backing up your private key. Your private key is stored in the Keychain, and will have to be exported first. Here’s how you do it:

  1. Launch Keychain Access, which lives in /Applications/Utilities.
  2. In the Category list on the left, select Keys.
  3. In the Keys list, select the keys used for code signing. They will have disclosure triangles next to them, which reveal iPhone Developer/Distribution certificates.
  4. From the menu, select File > Export Items. Be sure to save the key in the Personal Information Exchange (.p12) format.
  5. During the export, you will be prompted to create a password which will protect the exported file. Be sure to pick a secure password that you will either remember or that you will store in a safe place.

Now you have your private keys in a file that you can backup or check into source control. You will also probably want to backup the password you created in step 5.

Installing the private keys on a new machine is easy:

  1. Copy the exported file to the machine you want to install it on.
  2. Double click the .p12 file, which will launch Keychain Access.
  3. When prompted, entered the password you created in the previous step 5.

That’s all there is to it. Hopefully this will save someone pain in the future.

Filed in Programming, iPhone | 4 responses so far

How to get application data from the iPhone

andy on Aug 14th 2008

If you’ve written any kind of non-trivial application from the iPhone, you’ve had to deal with debugging it while not in the debugger. Sometimes you may even need to get at the preferences file and other support files, such as “saved state” files to see what went wrong.

There are two main ways to go about retrieving application data for your iPhone applications, depending on whose phone you want to get it off of. If it’s your personal phone you do development on, it’s pretty easy. If you need to get data from someone else, like say a beta user, it’s much more involved.

For your iPhone

If you’re on your development machine and iPhone, Xcode provides a pretty simple way to get at the data.

  1. First, dock your iPhone, then open Xcode.

  2. In Xcode, open the Organizer, via the Window > Organizer menu item.

  3. Select your iPhone in the source list on the left.

    OrganizerSourceList.png

  4. On the right, locate the Applications list. Find your application, and disclose the disclosure triangle to the left of the name.

    OrganizerApplications.png

  5. Select “Application Data” underneath the name of your application, and press the button on the right, the down arrow image.

  6. You will be prompted for a place to save it. Pick a place, and press “Save.”

You now have your application data onto your Mac, where you can debug it. You can either go to the Finder to browser it, or it is also added to the Organizer window in Xcode, under the “Projects & Sources” tree.

For a beta tester’s iPhone

If you are running a beta and you need to get data from your application to debug, things get pretty tricky. The general idea is you get your beta user to find the iTunes backup files (yes, more than one) that contain your application data, and have them send them to you. Then, using a third party python script, you uncompress the backup files, and hope that they actually contain the correct files.

  1. First, if the user does not yet have a backup (unlikely), they should force one. Do this by selecting the iPhone in iTunes, right clicking (or control-click) and selecting “Backup”.

    iTunesBackup.png

  2. Next, the beta user should open the Finder and go to ~/Library/Application Support/MobileSync/Backup. With any luck, there will be one folder here, which will be the 40 character hexadecimal device ID of their iPhone. Navigate inside this folder.

  3. In the Finder, switch to list view and sort by modification date.

    iTunesBackupFiles.png

  4. At this point, your beta user needs to select the appropriate backup files that contain the data you need (haha). Depending on your user, you may want them to just zip up the entire folder and send it to you. Be warned though — it’s huge.

    To be more selective, you only want the files ending in “mdbackup.” These files can be grouped based on modification date. For example, in the screenshot above, you can see that a backup happened Today, at 5:06 PM. The beta user could select all of the mdbackup files with a modification date of “Today, 5:06 PM”, zip them up, and email them to you.

    The thing to note here is that iTunes only does incremental backups. If your application data has not been modified since the previous backup, the current backup will not have your data in it. So you may need to repeat this step several times with your beta user until they find the right backup.

  5. At this point the beta user should zip up the “mdbackup” files, however you had them select them, and email them to you.

Things are now in your, the developer’s, hands. You need to convert the mdbackup files into something you can use.

  1. First, download iphone-backup-decoder from Google Code. iphone-backup-decode is a Python script that will unarchive all those mdbackup files you now have.

  2. Next, make a directory that you can work in, called “Backups” (or whatever you want to call it). Place all your mdbackup files in here, and move decode_iphone_backup_v2.1.py file here as well.

  3. Before you can run the python script, you need to make it executable. In Terminal.app, cd to your “Backups” folder, and type the following:

    chmod a+rx decode_iphone_backup_v2.1.py

  4. You are now ready to run the decoder, by typing in Terminal:

    ./decode_iphone_backup_v2.1.py *.mdbackup

    This will unarchive all mdbackup files current in the “Backup” folder, so keep that in mind if you’re going through multiple backups.

The results should now be in a folder called “MobileSyncExport” inside your “Backups” folder. If you are very lucky, it will contain the files you need.

If you need to repeat these steps, I would advise deleting all the mdbackup files and the MobileSyncExport folder from your Backups folder. The decoder script will most likely give undesirable results if you decode two backups that contain the same file.

Filed in Programming, iPhone | 14 responses so far

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