Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT * FROM blog_categories WHERE 1=1


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-blog-header.php on line 235

Database error: [Unknown column 'user_level' in 'where clause']
SELECT * FROM blog_users WHERE user_level > 0


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-blog-header.php on line 298

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT DISTINCT ID, category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat, blog_posts WHERE category_id = cat_ID AND post_id = ID AND post_id IN (82,79,76,73,71,69,64,60,43,40,38,33,31,28,18,16,14,13,12,11,10,8,9,7,6,5,4,3,1,2)


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-blog-header.php on line 478

Warning: Cannot modify header information - headers already sent by (output started at /usr/www/users/gotow/gotow.net/blog/wp-includes/wp-db.php:80) in /usr/www/users/gotow/gotow.net/blog/wp-rdf.php on line 9
Gotow.net - Blog http://www.gotow.net/creative/wordpress Just another WordPress weblog en 2012-04-25T07:22:39Z hourly 1 2000-01-01T12:00+00:00 Automatically generating Objective-C .h files from .m files http://www.gotow.net/creative/wordpress/?p=82 2012-04-25T07:14:04Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 82


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
Last weekend I participated in Hack Nashville and built a social pixel-art app called 46px. It was a fun project, but our team of four programmers hacking things together over the course of two days made quite a mess. We rushed to finish the app and one of the first things to go was .h/.m file consistency. Now I need to update most of the project’s .h files to remove warnings. In the past, this has amounted to: Copying the functions, body and all, from the .m file to the .h file. Removing everything in the brackets, adding a semicolon. Repeat for hundreds of functions. That workflow pretty much sucks. It’s not what I want to spend my day doing. I set about to do it a better way and discovered that it isn’t too hard to automate this mess with Automator and BBEdit’s GREP functionality. Cut to the chase, download the Workflow: Copy Function Headers.workflow Here’s the breakdown of Grep functions to perform the above: [objc] // remove all of the lines that are not pragma marks or method declarations 1. Find: ^(?!#pragma)((?!([-+]+[^;{]*)r).)*$r Replace With: <nothing> // Add a semicolon to the end of each method declaration 2. Find: ^([-+]+[^;{-+]*)r Replace With: 1;r // Remove any stray close brackets 3. Find: } Replace With: <nothing> // Identify pragma marks and add a leading and trailing carriage return 4. Find: (#pragma.*) Replace With: r1r // Find places where two or more lines of pragma marks in a row caused a series of three carriage returns in a row. 5. Find: rrr Replace With: r [/objc] I realize this is a hack—hell, that series of grep commands does and then _undoes_ some carriage returns. But at the end of the day, it works pretty well, and you get something like this: [objc] #pragma mark - #pragma mark UITableView Data Source Functions - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; - (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath; - (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)indexPath; #pragma mark - #pragma mark IBActions - (IBAction)helpIconTapped:(id)sender; - (IBAction)questionCountSliderChanged:(NumberRangeView*)slider; - (IBAction)operandRangeSliderChanged:(NumberRangeView*)slider; - (IBAction)timerSliderChanged:(NumberRangeView*)slider; [/objc] To use this Automator Workflow, double click to install it and then go to System Preferences > Keyboard and add a Keyboard Shortcut to Xcode. Tie a hotkey of your choice to the menu item “Copy Function Headers". The new workflow? Select the body of a .m file. Press the hotkey you assigned the action to. Paste into .h file. Enjoy! I hope this saves you as much time as it’s saved me. Download Copy Function Headers.workflow Last weekend I participated in Hack Nashville and built a social pixel-art app called 46px. It was a fun project, but our team of four programmers hacking things together over the course of two days made quite a mess. We rushed to finish the app and one of the first things to go was .h/.m file consistency.

Now I need to update most of the project’s .h files to remove warnings. In the past, this has amounted to:

  1. Copying the functions, body and all, from the .m file to the .h file.
  2. Removing everything in the brackets, adding a semicolon.
  3. Repeat for hundreds of functions.

That workflow pretty much sucks. It’s not what I want to spend my day doing. I set about to do it a better way and discovered that it isn’t too hard to automate this mess with Automator and BBEdit’s GREP functionality.

Cut to the chase, download the Workflow:
Copy Function Headers.workflow

Here’s the breakdown of Grep functions to perform the above:
[objc]
// remove all of the lines that are not pragma marks or method declarations
1. Find: ^(?!#pragma)((?!([-+]+[^;{]*)r).)*$r
Replace With: <nothing>

// Add a semicolon to the end of each method declaration
2. Find: ^([-+]+[^;{-+]*)r
Replace With: 1;r

// Remove any stray close brackets
3. Find: }
Replace With: <nothing>

// Identify pragma marks and add a leading and trailing carriage return
4. Find: (#pragma.*)
Replace With: r1r

// Find places where two or more lines of pragma marks in a row caused a series of three carriage returns in a row.
5. Find: rrr
Replace With: r
[/objc]

I realize this is a hack—hell, that series of grep commands does and then _undoes_ some carriage returns. But at the end of the day, it works pretty well, and you get something like this:

[objc]
#pragma mark -
#pragma mark UITableView Data Source Functions

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)indexPath;

#pragma mark -
#pragma mark IBActions

- (IBAction)helpIconTapped:(id)sender;
- (IBAction)questionCountSliderChanged:(NumberRangeView*)slider;
- (IBAction)operandRangeSliderChanged:(NumberRangeView*)slider;
- (IBAction)timerSliderChanged:(NumberRangeView*)slider;
[/objc]

To use this Automator Workflow, double click to install it and then go to System Preferences > Keyboard and add a Keyboard Shortcut to Xcode. Tie a hotkey of your choice to the menu item “Copy Function Headers".

The new workflow?

  1. Select the body of a .m file.
  2. Press the hotkey you assigned the action to.
  3. Paste into .h file.

Enjoy! I hope this saves you as much time as it’s saved me.

Download Copy Function Headers.workflow

]]>
PNG compression in Android… (You have got to be kidding) http://www.gotow.net/creative/wordpress/?p=79 2010-01-08T06:20:33Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 79


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
Over the last few weeks, I’ve been learning the Android SDK in an effort to bring Layers to Android devices. It’s going pretty well, but every once and a while I run into a truly WTF moment. Tonight I was importing some images from the iPhone version of Layers when I noticed that Android seems to visibly reduce the quality of PNG files at compile time. In images with fine gradients, smooth color transitions, or very light shadows you tend to get banding. It almost looks like the image were being converted to a GIF file. I figured it’d be easy to fix. Go into Eclipse, right click on everything, look in menus… repeat… profit! Unfortunately, it seems there’s no way to turn off compression for specific file or choose a non-lossy compression method. However, I found this gem of a solution in the Android Widget Design Guidelines: “To reduce banding when exporting a widget, apply the following Photoshop Add Noise setting to your graphic.” Um… what now? It turns out, you can get around the compression algorithm by adding a small amount of pixel noise to your images. It’s mostly invisible, and it prevents the compression from producing obvious bands. It’s an instant fix, but I almost laughed out loud. Seriously? This is the documented solution? *sigh*. Over the last few weeks, I’ve been learning the Android SDK in an effort to bring Layers to Android devices. It’s going pretty well, but every once and a while I run into a truly WTF moment.

Tonight I was importing some images from the iPhone version of Layers when I noticed that Android seems to visibly reduce the quality of PNG files at compile time. In images with fine gradients, smooth color transitions, or very light shadows you tend to get banding. It almost looks like the image were being converted to a GIF file.

I figured it’d be easy to fix. Go into Eclipse, right click on everything, look in menus… repeat… profit! Unfortunately, it seems there’s no way to turn off compression for specific file or choose a non-lossy compression method. However, I found this gem of a solution in the Android Widget Design Guidelines:

“To reduce banding when exporting a widget, apply the following Photoshop Add Noise setting to your graphic.”

Um… what now?

It turns out, you can get around the compression algorithm by adding a small amount of pixel noise to your images. It’s mostly invisible, and it prevents the compression from producing obvious bands.

It’s an instant fix, but I almost laughed out loud. Seriously? This is the documented solution? *sigh*.

]]>
iPhone Development Tutorial http://www.gotow.net/creative/wordpress/?p=76 2009-10-16T20:35:54Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 76


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
I gave a presentation within the Engineering school on Friday that gave a brief look at the iPhone platform and Objective-C. The end of the presentation was a quick tutorial in Interface Builder and XCode. You can download the presentation and the tutorial project here: iPhone Development Tutorial iPhoneDevelopmentTutorial.zip If you have any questions, feel free to email me at bengotow@gmail.com or post a comment. Also, be sure to check out layersforiphone.com. I gave a presentation within the Engineering school on Friday that gave a brief look at the iPhone platform and Objective-C. The end of the presentation was a quick tutorial in Interface Builder and XCode. You can download the presentation and the tutorial project here:
iPhone Development Tutorial

iPhoneDevelopmentTutorial.zip

If you have any questions, feel free to email me at bengotow@gmail.com or post a comment. Also, be sure to check out layersforiphone.com.

]]>
Layers featured in Macworld Article on iPhone Piracy! http://www.gotow.net/creative/wordpress/?p=73 2009-10-16T20:24:10Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 73


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
The piracy statistics I posted earlier this month have gathered some attention, and Nick Spence at Macworld.co.uk is running an article about the price of piracy for independent software developers. Check it out! iPhone App piracy: Small time developers feel the pinch UPDATE: The article was pushed to Macworld.com as well! The piracy statistics I posted earlier this month have gathered some attention, and Nick Spence at Macworld.co.uk is running an article about the price of piracy for independent software developers. Check it out!

iPhone App piracy: Small time developers feel the pinch

UPDATE: The article was pushed to Macworld.com as well!

]]>
App Store Piracy: Worse than you think. http://www.gotow.net/creative/wordpress/?p=71 2009-08-15T05:16:26Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 71


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
Two weeks ago, a minor update to Layers hit the App Store. The update included several important bug fixes and a few features, but one of the most major changes was the addition of a piracy tracking system. Each time the app is used on a jailbroken device, it phones home with a few (anonymized) metrics so that I can track the spread of pirated copies. Software on the Layers server gathers the data and prints out some cool statistics. Technically cool, that is. Not really “cool” at all. I’ve sold around 1,500 copies of Layers this week and in the same 7 day period, more than 1,780 copies have been pirated. It’s flattering, to some extent; people obviously enjoy the app. However, it’s also evidence to a much larger problem that I feel Apple continues to overlook. The DRM used in iPhone apps hasn’t been changed in ages, and an app on a jailbroken device can be automatically cracked using another iPhone app in a matter of seconds. No command line tools. No hand-editing files. You double click the app’s icon and it cracks it. Done. For “expensive” apps like Layers, piracy is an especially significant problem. The latest version of Layers runs about 22,000 lines of code, and my community and target market are small. Everyone needs to chip in so I can recoup the cost of development and rationalize extra time spent improving the app. The App Store’s layout and “Top 100″ formatting encourage 99¢ apps with limited utility, so it’s difficult to market a $4.99 drawing app to begin with. (I’ve been lucky enough to be a Staff Favorite on the App Store) Piracy rates above 100% really don’t help. So what do you do? I feel it’s absolutely necessary to weed out the pirates. I wouldn’t mind providing illegitimate users with a time-limited or feature-limited version of the app. The problem is, current methods of screening for pirated copies are binary-dependent and patchable. In about a week, Layers will start displaying notices to pirates asking them to upgrade their “demo” copies of the app to a full version or “buy me a beer.” I hope that a few people will appreciate the app enough after using the pirated copy to consider paying. Even a 5% pirated-to-paid conversion rate would be an extra 15 sales a day. In the future, I’d like to see Apple implement a secure model for confirming that users are licensed to use an app. A secure receipts model is built in to the In-App Purchase system, and I don’t see why it wouldn’t work for the app as a whole. The app would establish a secure connection to an iTunes server, exchange product identifiers and account details, and verify that a product had in fact been purchased. I don’t think Apple will implement anything in the near future, because it would require admitting that piracy was, in fact, an issue. We can dream, though. A few other developers I’ve talked to have suggested creating a repository of device identifiers that have been nabbed during phone-home routines. It seems like a good idea, but I understand there’s some hesitation to start calling people pirates left and right. Apps would need to pre-emptively contact the repository, and a simple change to the UNIX hosts file could break the system. So for now, It looks like I’ll be dreaming of an extra 1,700 sales a week. Two weeks ago, a minor update to Layers hit the App Store. The update included several important bug fixes and a few features, but one of the most major changes was the addition of a piracy tracking system. Each time the app is used on a jailbroken device, it phones home with a few (anonymized) metrics so that I can track the spread of pirated copies. Software on the Layers server gathers the data and prints out some cool statistics.

Technically cool, that is. Not really “cool” at all. I’ve sold around 1,500 copies of Layers this week and in the same 7 day period, more than 1,780 copies have been pirated. It’s flattering, to some extent; people obviously enjoy the app. However, it’s also evidence to a much larger problem that I feel Apple continues to overlook. The DRM used in iPhone apps hasn’t been changed in ages, and an app on a jailbroken device can be automatically cracked using another iPhone app in a matter of seconds. No command line tools. No hand-editing files. You double click the app’s icon and it cracks it. Done.

For “expensive” apps like Layers, piracy is an especially significant problem. The latest version of Layers runs about 22,000 lines of code, and my community and target market are small. Everyone needs to chip in so I can recoup the cost of development and rationalize extra time spent improving the app. The App Store’s layout and “Top 100″ formatting encourage 99¢ apps with limited utility, so it’s difficult to market a $4.99 drawing app to begin with. (I’ve been lucky enough to be a Staff Favorite on the App Store) Piracy rates above 100% really don’t help.

So what do you do? I feel it’s absolutely necessary to weed out the pirates. I wouldn’t mind providing illegitimate users with a time-limited or feature-limited version of the app. The problem is, current methods of screening for pirated copies are binary-dependent and patchable. In about a week, Layers will start displaying notices to pirates asking them to upgrade their “demo” copies of the app to a full version or “buy me a beer.” I hope that a few people will appreciate the app enough after using the pirated copy to consider paying. Even a 5% pirated-to-paid conversion rate would be an extra 15 sales a day.

In the future, I’d like to see Apple implement a secure model for confirming that users are licensed to use an app. A secure receipts model is built in to the In-App Purchase system, and I don’t see why it wouldn’t work for the app as a whole. The app would establish a secure connection to an iTunes server, exchange product identifiers and account details, and verify that a product had in fact been purchased. I don’t think Apple will implement anything in the near future, because it would require admitting that piracy was, in fact, an issue. We can dream, though.

A few other developers I’ve talked to have suggested creating a repository of device identifiers that have been nabbed during phone-home routines. It seems like a good idea, but I understand there’s some hesitation to start calling people pirates left and right. Apps would need to pre-emptively contact the repository, and a simple change to the UNIX hosts file could break the system.

So for now, It looks like I’ll be dreaming of an extra 1,700 sales a week.

]]>
4 1/2 stars! But you can’t compete with sex… http://www.gotow.net/creative/wordpress/?p=69 2009-08-04T18:13:39Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 69


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
Layers finally dropped off the list of top paid entertainment apps on the App Store this week. It’s not all that surprising - for such an expensive app, I was happy to see it on there at all! I think this screenshot pretty much sums up my feelings, though. Of the top 100 entertainment apps, 13 of them are some variant of “hot babe” apps. Looks like you just can’t compete with sex. I’ve heard rumors that Apple is working on a new way of presenting apps that will showcase the more complex apps, and it couldn’t be coming soon enough. Looking at the top 100 lists just makes me lose faith in humanity. It seems like I should be writing hot babe slideshows and fart apps, but I just can’t bring myself to do it… Layers finally dropped off the list of top paid entertainment apps on the App Store this week. It’s not all that surprising - for such an expensive app, I was happy to see it on there at all! I think this screenshot pretty much sums up my feelings, though. Of the top 100 entertainment apps, 13 of them are some variant of “hot babe” apps. Looks like you just can’t compete with sex.

Layers - Not hot babes!

I’ve heard rumors that Apple is working on a new way of presenting apps that will showcase the more complex apps, and it couldn’t be coming soon enough. Looking at the top 100 lists just makes me lose faith in humanity. It seems like I should be writing hot babe slideshows and fart apps, but I just can’t bring myself to do it…

]]>
EXIF Orientation Flags and the iPhone Camera http://www.gotow.net/creative/wordpress/?p=64 2009-07-26T03:50:15Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 64


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
Alright, I have a rant. Bear with me… Layers just came out this past Monday, and it has this great feature that allows you to add a layer to a drawing from your iPhone’s photo library. Simple enough - right? Apple provides the UIImagePicker API, we call a couple functions and get an image back. For most purposes, that would work great! Write some code, test, commit, done. The problem is, the picker interface allows the user to adjust the scale/positioning of the image, and the cropped image is always returned at 320x320px (or less). 320px is really quite pathetic, and it means the images are smaller than the 512x512px drawing canvas in Layers. I could scale up each photo when it’s added to the drawing, but that’d be pretty lame. The UIImagePicker API provides an editInfo dictionary containing the original image and the cropping rect information, so I decided to grab the original and re-perform the adjustments. Using the cropRect provided by the API, I could just re-crop the large, original image to 512x512… right? Unfortunately, no. Photos taken with the iPhone’s camera use the industry-standard EXIF orientation flag to store rotation information. That means that the image data is always saved upright, and it’s the client application’s job to realize it should be rotated 90º, 180º or 270º because the user was holding the camera upside down or sideways. Technically, this is great. The problem is, the editInfo dictionary contains (1) the original image and (2) the crop rect, defined in the coordinate space of the image after the EXIF orientation flag is taken into account. You can’t just jump in and crop the original image, because one has had transformations applied and the other hasn’t. So there are two options: - Option 1: Rotate the original image you’re given based on the EXIF data, and then crop it using the cropRect. This is slow because you have to rotate the entire image and then you end up throwing most of it away. For extremely large images (which can be added into your photo library via Mail attachments), it fails entirely. - Option 2: Adjust the cropping rect and undo the transformations that have been applied to it based on the EXIF data. This is better, but it requires writing some nasty CGRect transformations and lots of boxes drawn on paper. I decided to go with option 2. I wrote a nice big switch statement to undo the transformations for each of the eight possible EXIF values. But then I discovered something else: Somebody was lazy. The iPhone’s Photos application only understands EXIF orientations 1, 3, 6, and 8. These correspond to the common orientations: UIImageOrientationUp, UIImageOrientationDown, UIImageOrientationLeft, and UIImageOrientationRight. Photos with the other four orientations (the “mirrored” ones) appear unrotated in the photo browser. (See screenshot at right. Numbers on the images correspond to their EXIF orientation values). I want the user to get what they expect to get when they add a photo - even if it isn’t what they want. If the image is sideways while they’re cropping it, it should still be sideways when they press OK. I promptly deleted code for the other orientations so as to handle them as badly as the photo browser. Cool. moving on… The image picker allows the user to zoom in on the image of their choosing and pan around it, but the pan functionality is broken. You can pan beyond the edge of the image along the vertical axis, so that image is only partially visible within the gray cropping rectangle. This can lead to some strange results. A cropRect of (0,0,320,200) for an image of size (512,512), for example, indicates that a black gap is present at the top of the crop region. After playing around with this for a while, I was able to figure out how to differentiate between the cropRect values and properly draw the image to appear exactly as it did in the preview. Fixing this problem took almost 5 hours - and it really shouldn’t have. Here’s the code that takes the original image and cropRect and re-performs the adjustments to yield a 512x512 cropped image that matches exactly what the user saw when they clicked “Choose” in the picker: [objc] - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img editingInfo:(NSDictionary *)editInfo { if ([picker sourceType] == UIImagePickerControllerSourceTypeCamera){ // save the image to the photo library UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil); } [self dismissModalViewControllerAnimated:YES]; NSDictionary * assets = [NSDictionary dictionaryWithObjectsAndKeys:img, @"smallCroppedImage", editInfo, @"editInfo", nil]; [self performSelector:@selector(imagePickerControllerDidFinishThreaded:) withObject:assets afterDelay:0.05]; } - (void)imagePickerControllerDidFinishThreaded:(NSDictionary*)assets { NSDictionary * editInfo = [assets objectForKey: @"editInfo"]; CGRect editCropRect = [[editInfo valueForKey:UIImagePickerControllerCropRect] CGRectValue]; // 1. Determine original image orientation and size UIImage * originalImage = [editInfo valueForKey: UIImagePickerControllerOriginalImage]; UIImageOrientation originalOrientation = originalImage.imageOrientation; CGSize originalSize = originalImage.size; CGSize desiredSize = CGSizeMake(512,512); // 2. Modify crop rect to reflect image orientation CGFloat oldY = editCropRect.origin.y; CGFloat oldOriginalW = originalSize.width; CGFloat tmp; switch (originalOrientation) { case UIImageOrientationUp: //EXIF 1 break; case UIImageOrientationDown: //EXIF 3 // X flipped horizontally // Y flipped vertically editCropRect.origin.x = originalSize.width - (editCropRect.size.width + editCropRect.origin.x); editCropRect.origin.y = originalSize.height - (editCropRect.size.height + editCropRect.origin.y); break; case UIImageOrientationLeft: //EXIF 6 // fix info for original image. originalSize.width = originalSize.height; originalSize.height = oldOriginalW; // fix crop rect tmp = editCropRect.size.height; editCropRect.size.height = editCropRect.size.width; editCropRect.size.width = tmp; // rotation to the left editCropRect.origin.y = originalSize.height - (editCropRect.origin.x + editCropRect.size.height); editCropRect.origin.x = oldY; break; case UIImageOrientationRight: //EXIF 8 // fix info for original image. originalSize.width = originalSize.height; originalSize.height = oldOriginalW; // fix crop rect tmp = editCropRect.size.height; editCropRect.size.height = editCropRect.size.width; editCropRect.size.width = tmp; // rotate to the right editCropRect.origin.y = editCropRect.origin.x; editCropRect.origin.x = originalSize.height - oldY; break; default: break; } // 2.5. make the damn thing square if it’s ALMOST square if (fabs((editCropRect.size.height - editCropRect.size.width) / fminf(originalSize.height, originalSize.width)) < 0.0295){ editCropRect.size.width = fminf(editCropRect.size.width, editCropRect.size.height); editCropRect.size.height = editCropRect.size.width; } // 3. Crop image using crop rect UIGraphicsBeginImageContext(desiredSize); CGContextRef context = UIGraphicsGetCurrentContext(); CGImageRef image = CGImageCreateWithImageInRect([originalImage CGImage], editCropRect); CGRect imageRect = CGRectMake(0.0f, 0.0f, desiredSize.width, desiredSize.height); // Image width < Image height. Just center vertically if (editCropRect.size.width / editCropRect.size.height < 1){ imageRect.origin.x = (desiredSize.width - editCropRect.size.width * desiredSize.height/editCropRect.size.height)/2; imageRect.size.width -= imageRect.origin.x * 2; // Image width > Image height } else if (editCropRect.size.width / editCropRect.size.height > 1){ float extraHeight = desiredSize.height - editCropRect.size.height * (desiredSize.width / editCropRect.size.width); // If the crop rect’s origin is at the top of the screen, some of it might be clear (IE, the user may // have dragged “too far” and have some white space at the top of the preview box if (editCropRect.origin.y == 0) { imageRect.size.height -= extraHeight; if (roundf(editCropRect.size.height) == roundf(originalSize.height)) imageRect.origin.y = extraHeight / 2; else imageRect.origin.y = 0; // User dragged “too far” down, and white space is visible at the bottom of preview box } else if (fabs(editCropRect.origin.y - (originalSize.height - roundf(editCropRect.size.height))) Alright, I have a rant. Bear with me…

Layers just came out this past Monday, and it has this great feature that allows you to add a layer to a drawing from your iPhone’s photo library. Simple enough - right? Apple provides the UIImagePicker API, we call a couple functions and get an image back.

For most purposes, that would work great! Write some code, test, commit, done. The problem is, the picker interface allows the user to adjust the scale/positioning of the image, and the cropped image is always returned at 320x320px (or less). 320px is really quite pathetic, and it means the images are smaller than the 512x512px drawing canvas in Layers. I could scale up each photo when it’s added to the drawing, but that’d be pretty lame.

The UIImagePicker API provides an editInfo dictionary containing the original image and the cropping rect information, so I decided to grab the original and re-perform the adjustments. Using the cropRect provided by the API, I could just re-crop the large, original image to 512x512… right?

Unfortunately, no. Photos taken with the iPhone’s camera use the industry-standard EXIF orientation flag to store rotation information. That means that the image data is always saved upright, and it’s the client application’s job to realize it should be rotated 90º, 180º or 270º because the user was holding the camera upside down or sideways.

Technically, this is great. The problem is, the editInfo dictionary contains (1) the original image and (2) the crop rect, defined in the coordinate space of the image after the EXIF orientation flag is taken into account. You can’t just jump in and crop the original image, because one has had transformations applied and the other hasn’t. So there are two options:

- Option 1: Rotate the original image you’re given based on the EXIF data, and then crop it using the cropRect. This is slow because you have to rotate the entire image and then you end up throwing most of it away. For extremely large images (which can be added into your photo library via Mail attachments), it fails entirely.
- Option 2: Adjust the cropping rect and undo the transformations that have been applied to it based on the EXIF data. This is better, but it requires writing some nasty CGRect transformations and lots of boxes drawn on paper.

I decided to go with option 2. I wrote a nice big switch statement to undo the transformations for each of the eight possible EXIF values. But then I discovered something else:

iPhone Photos App with Different EXIF Orientation FlagsSomebody was lazy. The iPhone’s Photos application only understands EXIF orientations 1, 3, 6, and 8. These correspond to the common orientations: UIImageOrientationUp, UIImageOrientationDown, UIImageOrientationLeft, and UIImageOrientationRight. Photos with the other four orientations (the “mirrored” ones) appear unrotated in the photo browser. (See screenshot at right. Numbers on the images correspond to their EXIF orientation values).

I want the user to get what they expect to get when they add a photo - even if it isn’t what they want. If the image is sideways while they’re cropping it, it should still be sideways when they press OK. I promptly deleted code for the other orientations so as to handle them as badly as the photo browser. Cool. moving on…

iPhone Photos App - Weird Vertical Pan BehaviorThe image picker allows the user to zoom in on the image of their choosing and pan around it, but the pan functionality is broken. You can pan beyond the edge of the image along the vertical axis, so that image is only partially visible within the gray cropping rectangle. This can lead to some strange results. A cropRect of (0,0,320,200) for an image of size (512,512), for example, indicates that a black gap is present at the top of the crop region. After playing around with this for a while, I was able to figure out how to differentiate between the cropRect values and properly draw the image to appear exactly as it did in the preview.

Fixing this problem took almost 5 hours - and it really shouldn’t have. Here’s the code that takes the original image and cropRect and re-performs the adjustments to yield a 512x512 cropped image that matches exactly what the user saw when they clicked “Choose” in the picker:

[objc]
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)img editingInfo:(NSDictionary *)editInfo
{
if ([picker sourceType] == UIImagePickerControllerSourceTypeCamera){
// save the image to the photo library
UIImageWriteToSavedPhotosAlbum(img, nil, nil, nil);
}

[self dismissModalViewControllerAnimated:YES];

NSDictionary * assets = [NSDictionary dictionaryWithObjectsAndKeys:img, @"smallCroppedImage", editInfo, @"editInfo", nil];
[self performSelector:@selector(imagePickerControllerDidFinishThreaded:) withObject:assets afterDelay:0.05];
}

- (void)imagePickerControllerDidFinishThreaded:(NSDictionary*)assets
{
NSDictionary * editInfo = [assets objectForKey: @"editInfo"];
CGRect editCropRect = [[editInfo valueForKey:UIImagePickerControllerCropRect] CGRectValue];

// 1. Determine original image orientation and size
UIImage * originalImage = [editInfo valueForKey: UIImagePickerControllerOriginalImage];
UIImageOrientation originalOrientation = originalImage.imageOrientation;
CGSize originalSize = originalImage.size;
CGSize desiredSize = CGSizeMake(512,512);

// 2. Modify crop rect to reflect image orientation
CGFloat oldY = editCropRect.origin.y;
CGFloat oldOriginalW = originalSize.width;
CGFloat tmp;

switch (originalOrientation) {
case UIImageOrientationUp: //EXIF 1
break;

case UIImageOrientationDown: //EXIF 3
// X flipped horizontally
// Y flipped vertically
editCropRect.origin.x = originalSize.width - (editCropRect.size.width + editCropRect.origin.x);
editCropRect.origin.y = originalSize.height - (editCropRect.size.height + editCropRect.origin.y);
break;

case UIImageOrientationLeft: //EXIF 6
// fix info for original image.
originalSize.width = originalSize.height;
originalSize.height = oldOriginalW;

// fix crop rect
tmp = editCropRect.size.height;
editCropRect.size.height = editCropRect.size.width;
editCropRect.size.width = tmp;

// rotation to the left
editCropRect.origin.y = originalSize.height - (editCropRect.origin.x + editCropRect.size.height);
editCropRect.origin.x = oldY;
break;

case UIImageOrientationRight: //EXIF 8
// fix info for original image.
originalSize.width = originalSize.height;
originalSize.height = oldOriginalW;

// fix crop rect
tmp = editCropRect.size.height;
editCropRect.size.height = editCropRect.size.width;
editCropRect.size.width = tmp;

// rotate to the right
editCropRect.origin.y = editCropRect.origin.x;
editCropRect.origin.x = originalSize.height - oldY;
break;

default:
break;
}

// 2.5. make the damn thing square if it’s ALMOST square
if (fabs((editCropRect.size.height - editCropRect.size.width) / fminf(originalSize.height, originalSize.width)) < 0.0295){
editCropRect.size.width = fminf(editCropRect.size.width, editCropRect.size.height);
editCropRect.size.height = editCropRect.size.width;
}

// 3. Crop image using crop rect
UIGraphicsBeginImageContext(desiredSize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGImageRef image = CGImageCreateWithImageInRect([originalImage CGImage], editCropRect);
CGRect imageRect = CGRectMake(0.0f, 0.0f, desiredSize.width, desiredSize.height);

// Image width < Image height. Just center vertically
if (editCropRect.size.width / editCropRect.size.height < 1){
imageRect.origin.x = (desiredSize.width - editCropRect.size.width * desiredSize.height/editCropRect.size.height)/2;
imageRect.size.width -= imageRect.origin.x * 2;

// Image width > Image height
} else if (editCropRect.size.width / editCropRect.size.height > 1){
float extraHeight = desiredSize.height - editCropRect.size.height * (desiredSize.width / editCropRect.size.width);

// If the crop rect’s origin is at the top of the screen, some of it might be clear (IE, the user may
// have dragged “too far” and have some white space at the top of the preview box
if (editCropRect.origin.y == 0) {
imageRect.size.height -= extraHeight;
if (roundf(editCropRect.size.height) == roundf(originalSize.height))
imageRect.origin.y = extraHeight / 2;
else
imageRect.origin.y = 0;

// User dragged “too far” down, and white space is visible at the bottom of preview box
} else if (fabs(editCropRect.origin.y - (originalSize.height - roundf(editCropRect.size.height))) <= 1.1) {
imageRect.origin.y = extraHeight;
imageRect.size.height -= extraHeight;

}else {
imageRect.origin.y = (desiredSize.height - editCropRect.size.height * desiredSize.width/editCropRect.size.width)/2;
imageRect.size.height -= imageRect.origin.y * 2;
}
}

CGContextClearRect(context, CGRectMake(0,0,desiredSize.width,desiredSize.height));
CGContextDrawImage(context, imageRect, image);
UIImage* croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRelease(image);

// 4. Perform image rotation
UIImage * finalImage = [self rotateImage: croppedImage byOrientationFlag: originalOrientation];

// DO SOMETHING WITH finalImage!
}

#pragma mark Convenience Functions for Image Picking

- (UIImage*)rotateImage:(UIImage*)img byOrientationFlag:(UIImageOrientation)orient
{
CGImageRef imgRef = img.CGImage;
CGFloat width = CGImageGetWidth(imgRef);
CGFloat height = CGImageGetHeight(imgRef);
CGAffineTransform transform = CGAffineTransformIdentity;
CGRect bounds = CGRectMake(0, 0, width, height);
CGSize imageSize = bounds.size;
CGFloat boundHeight;

switch(orient) {

case UIImageOrientationUp: //EXIF = 1
transform = CGAffineTransformIdentity;
break;

case UIImageOrientationDown: //EXIF = 3
transform = CGAffineTransformMakeTranslation(imageSize.width, imageSize.height);
transform = CGAffineTransformRotate(transform, M_PI);
break;

case UIImageOrientationLeft: //EXIF = 6
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(imageSize.height, imageSize.width);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
break;

case UIImageOrientationRight: //EXIF = 8
boundHeight = bounds.size.height;
bounds.size.height = bounds.size.width;
bounds.size.width = boundHeight;
transform = CGAffineTransformMakeTranslation(0.0, imageSize.width);
transform = CGAffineTransformRotate(transform, 3.0 * M_PI / 2.0);
break;

default:
// image is not auto-rotated by the photo picker, so whatever the user
// sees is what they expect to get. No modification necessary
transform = CGAffineTransformIdentity;
break;

}

UIGraphicsBeginImageContext(bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();

if ((orient == UIImageOrientationDown) || (orient == UIImageOrientationRight) || (orient == UIImageOrientationUp)){
// flip the coordinate space upside down
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -height);
}

CGContextConcatCTM(context, transform);
CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height), imgRef);
UIImage *imageCopy = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

return imageCopy;
}
[/objc]

I hope that it saves you time - please leave a comment below if you find it useful! Also - I’ve attached a ZIP file with eight images that can be used to debug problems with EXIF orientation handling. Each image has a different EXIF orientation flag value, and a giant number in the center of the image lets you know what it is. On the Mac desktop, all eight will appear to be vertical because QuickLook properly adjusts them based on their EXIF values. Other apps, like Fireworks, will open them sideways, upside-down, backwards, etc… Enjoy!

Download EXIF Orientation Sample Images (44MB)

]]>
Mac OS X Stack Overflow Status Item! http://www.gotow.net/creative/wordpress/?p=60 2009-07-06T04:08:01Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 60


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
I’ve become a huge fan of Stack Overflow over the last few weeks. The community there is helpful and fast and there are quite a few questions about Cocoa and Objective-C! It’s gotten to the point where I visit SO whenever my code is compiling - so I thought it was time to take matters into my own hands and make things easier to follow. I’ve made a Stack Overflow status item for Mac OS X (also a “menu bar item” or a “system icon") that shows your reputation and lists questions on the front page containing your interesting tags: It’s pretty primitive at this point - you can click a question to view it, or click the tiny arrow to go to your user page. It updates every 90 seconds using the RSS and ATOM feeds from the site, so the most active questions are always available at a glance. It’s compatible with Mac OS X 10.5 and 10.6 - so download it and give it a shot! I know there are a lot of things that could be added - so leave a comment and let me know what you think. Download the Stack Overflow Status Item for Mac OS X (0.5 MB) I’ve become a huge fan of Stack Overflow over the last few weeks. The community there is helpful and fast and there are quite a few questions about Cocoa and Objective-C! It’s gotten to the point where I visit SO whenever my code is compiling - so I thought it was time to take matters into my own hands and make things easier to follow.

I’ve made a Stack Overflow status item for Mac OS X (also a “menu bar item” or a “system icon") that shows your reputation and lists questions on the front page containing your interesting tags:

SO Status Item in Action

It’s pretty primitive at this point - you can click a question to view it, or click the tiny arrow to go to your user page. It updates every 90 seconds using the RSS and ATOM feeds from the site, so the most active questions are always available at a glance. It’s compatible with Mac OS X 10.5 and 10.6 - so download it and give it a shot! I know there are a lot of things that could be added - so leave a comment and let me know what you think.

Download the Stack Overflow Status Item for Mac OS X (0.5 MB)

]]>
PackBits algorithm in Objective-C http://www.gotow.net/creative/wordpress/?p=43 2009-05-25T05:09:18Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 43


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
The PackBits algorithm is one of the TIFF data compression methods, and it’s also used for pixel data in Photoshop PSD and TGA files. It was originally developed for MacPaint, and although it’s still widely used, there isn’t a whole lot of information online about it. I spent some time this weekend writing a category to extend the NSData class and support the packBits algorithm, and I think I’ve finally got it. PackBits is really dirt simple. If you have a string of hex values like 00 00 00 FF FF FF FF, it replaces series of  3 or more identical bytes with a count, and then the repeated byte. The input above would be 03 00 04 FF. A prefix is also attached to series of different bytes so the decoder knows how many bytes to pass through before looking for another header byte. The full documentation of PackBits can be found on Wikipedia. My implementation is a modified version of the one available here: http://michael.dipperstein.com/rle/index.html (source link at very bottom of page). That implementation is a modification of the official PackBits algorithm that allows slightly larger runs of data to be encoded in a single header + byte pair. I was interested in the original implementation only (since data written from any customized encoder could not be opened by a standard decoder) so I changed it back to the standard algorithm. The primary function provided in the PackBitsAdditions category below is: [objc] - (NSData*)packedBitsForRange:(NSRange)range skip:(int)skip[/objc] This function returns the packBits representation of the data in “range", advancing “skip” bytes with each read. For instance, to read and encode every byte in “range", you would provide skip = 1. To read and encode only every 4th byte, you would pass skip = 4. This may seem like an odd implementation, but it’s very handy when you need to encode the channels of an RGBA image separately (as in PSD format). The other function in the category is packedBitsDescription. It describes the packed bits and the process that would be followed to decode them. This function could be easily extended to actually decode the data. If you find this code useful, please leave a comment! I debugged this for quite a while, and I’d be happy to help if you run into any issues with my implementation! NSDataPackBitsAdditions.h [objc] @interface NSData (PackBitsAdditions) - (NSString*)packedBitsDescription; - (NSData*)packedBitsForRange:(NSRange)range skip:(int)skip; @end [/objc] NSDataPackBitsAdditions.m [objc] @implementation NSData (PackBitsAdditions) - (NSString*)packedBitsDescription { NSMutableString * description = [NSMutableString string]; char * row = (char*)[self bytes]; int pbOffset = 0; int pbResultBytes = 0; while (pbOffset < [self length]){ int headerByte = (int)row[pbOffset]; if (headerByte < 0){ int repeatTimes = 1-headerByte; UInt8 repeatByte = (UInt8)row[pbOffset+1]; [description appendFormat: @"Printing %u %d times. ", repeatByte, repeatTimes]; pbResultBytes += repeatTimes; pbOffset += 2; } else if (headerByte >= 0){ [description appendFormat: @"Printing %d literal bytes. ", headerByte + 1]; pbResultBytes += headerByte + 1; pbOffset += 2 + headerByte; } } [description appendFormat: @"Total: %d bytes decoded.", pbResultBytes]; return description; } - (NSData*)packedBitsForRange:(NSRange)range skip:(int)skip { char * bytesIn = [self bytes]; int bytesLength = range.location + range.length; int bytesOffset = range.location; NSMutableData * dataOut = [NSMutableData data]; BOOL currIsEOF = NO; unsigned char currChar; /* current character */ unsigned char charBuf[MAX_READ]; /* buffer of already read characters */ int count; /* number of characters in a run */ /* prime the read loop */ currChar = bytesIn[bytesOffset]; bytesOffset = bytesOffset + skip; count = 0; /* read input until there’s nothing left */ while (!currIsEOF) { charBuf[count] = (unsigned char)currChar; count++; if (count >= MIN_RUN){ int i; /* check for run charBuf[count - 1] .. charBuf[count - MIN_RUN]*/ for (i = 2; i <= MIN_RUN; i++){ if (currChar != charBuf[count - i]){ /* no run */ i = 0; break; } } if (i != 0){ /* we have a run write out buffer before run*/ int nextChar; if (count > MIN_RUN){ /* block size - 1 followed by contents */ UInt8 a = count - MIN_RUN - 1; [dataOut appendBytes:&a length:sizeof(UInt8)]; [dataOut appendBytes:&charBuf length:sizeof(unsigned char) * (count - MIN_RUN)]; } /* determine run length (MIN_RUN so far) */ count = MIN_RUN; while (true){ if (bytesOffset < bytesLength){ nextChar = bytesIn[bytesOffset]; bytesOffset += skip; } else { currIsEOF = YES; nextChar = EOF; } if (nextChar != currChar) break; count++; if (count == MAX_RUN){ /* run is at max length */ break; } } /* write out encoded run length and run symbol */ UInt8 a = ((int)(1 - (int)(count))); [dataOut appendBytes:&a length:sizeof(UInt8)]; [dataOut appendBytes:&currChar length:sizeof(UInt8)]; if ((!currIsEOF) && (count != MAX_RUN)){ /* make run breaker start of next buffer */ charBuf[0] = nextChar; count = 1; } else { /* file or max run ends in a run */ count = 0; } } } if (count == MAX_READ){ int i; /* write out buffer */ UInt8 a = MAX_COPY - 1; [dataOut appendBytes:&a length:sizeof(UInt8)]; [dataOut appendBytes:&charBuf[0] length:sizeof(unsigned char) * MAX_COPY]; /* start a new buffer */ count = MAX_READ - MAX_COPY; /* copy excess to front of buffer */ for (i = 0; i < count; i++) charBuf[i] = charBuf[MAX_COPY + i]; } if (bytesOffset < bytesLength) currChar = bytesIn[bytesOffset]; else currIsEOF = YES; bytesOffset += skip; } /* write out last buffer */ if (0 != count){ if (count <= MAX_COPY){ /* write out entire copy buffer */ UInt8 a = count - 1; [dataOut appendBytes:&a length:sizeof(UInt8)]; [dataOut appendBytes:&charBuf length:sizeof(unsigned char) * count]; } else { /* we read more than the maximum for a single copy buffer */ UInt8 a = MAX_COPY - 1; [dataOut appendBytes:&a length:sizeof(UInt8)]; [dataOut appendBytes:&charBuf length:sizeof(unsigned char) * MAX_COPY]; /* write out remainder */ count -= MAX_COPY; a = count - 1; [dataOut appendBytes:&a length:sizeof(UInt8)]; [dataOut appendBytes:&charBuf[MAX_COPY] length:sizeof(unsigned char) * count]; } } return dataOut; } @end [/objc] The PackBits algorithm is one of the TIFF data compression methods, and it’s also used for pixel data in Photoshop PSD and TGA files. It was originally developed for MacPaint, and although it’s still widely used, there isn’t a whole lot of information online about it. I spent some time this weekend writing a category to extend the NSData class and support the packBits algorithm, and I think I’ve finally got it.

PackBits is really dirt simple. If you have a string of hex values like 00 00 00 FF FF FF FF, it replaces series of  3 or more identical bytes with a count, and then the repeated byte. The input above would be 03 00 04 FF. A prefix is also attached to series of different bytes so the decoder knows how many bytes to pass through before looking for another header byte.

The full documentation of PackBits can be found on Wikipedia. My implementation is a modified version of the one available here: http://michael.dipperstein.com/rle/index.html (source link at very bottom of page). That implementation is a modification of the official PackBits algorithm that allows slightly larger runs of data to be encoded in a single header + byte pair. I was interested in the original implementation only (since data written from any customized encoder could not be opened by a standard decoder) so I changed it back to the standard algorithm.

The primary function provided in the PackBitsAdditions category below is:

[objc] - (NSData*)packedBitsForRange:(NSRange)range skip:(int)skip[/objc]

This function returns the packBits representation of the data in “range", advancing “skip” bytes with each read. For instance, to read and encode every byte in “range", you would provide skip = 1. To read and encode only every 4th byte, you would pass skip = 4. This may seem like an odd implementation, but it’s very handy when you need to encode the channels of an RGBA image separately (as in PSD format).

The other function in the category is packedBitsDescription. It describes the packed bits and the process that would be followed to decode them. This function could be easily extended to actually decode the data.

If you find this code useful, please leave a comment! I debugged this for quite a while, and I’d be happy to help if you run into any issues with my implementation!

NSDataPackBitsAdditions.h

[objc]

@interface NSData (PackBitsAdditions)

- (NSString*)packedBitsDescription;
- (NSData*)packedBitsForRange:(NSRange)range skip:(int)skip;

@end
[/objc]

NSDataPackBitsAdditions.m

[objc]
@implementation NSData (PackBitsAdditions)

- (NSString*)packedBitsDescription
{
NSMutableString * description = [NSMutableString string];
char * row = (char*)[self bytes];
int pbOffset = 0;
int pbResultBytes = 0;

while (pbOffset < [self length]){
int headerByte = (int)row[pbOffset];
if (headerByte < 0){
int repeatTimes = 1-headerByte;
UInt8 repeatByte = (UInt8)row[pbOffset+1];
[description appendFormat: @"Printing %u %d times. ", repeatByte, repeatTimes];

pbResultBytes += repeatTimes;
pbOffset += 2;
} else if (headerByte >= 0){
[description appendFormat: @"Printing %d literal bytes. ", headerByte + 1];
pbResultBytes += headerByte + 1;
pbOffset += 2 + headerByte;
}
}

[description appendFormat: @"Total: %d bytes decoded.", pbResultBytes];
return description;
}

- (NSData*)packedBitsForRange:(NSRange)range skip:(int)skip
{
char * bytesIn = [self bytes];
int bytesLength = range.location + range.length;
int bytesOffset = range.location;
NSMutableData * dataOut = [NSMutableData data];

BOOL currIsEOF = NO;
unsigned char currChar; /* current character */
unsigned char charBuf[MAX_READ]; /* buffer of already read characters */
int count; /* number of characters in a run */

/* prime the read loop */
currChar = bytesIn[bytesOffset];
bytesOffset = bytesOffset + skip;
count = 0;

/* read input until there’s nothing left */
while (!currIsEOF)
{
charBuf[count] = (unsigned char)currChar;
count++;

if (count >= MIN_RUN){
int i;

/* check for run charBuf[count - 1] .. charBuf[count - MIN_RUN]*/
for (i = 2; i <= MIN_RUN; i++){
if (currChar != charBuf[count - i]){
/* no run */
i = 0;
break;
}
}

if (i != 0){
/* we have a run write out buffer before run*/
int nextChar;

if (count > MIN_RUN){
/* block size - 1 followed by contents */
UInt8 a = count - MIN_RUN - 1;
[dataOut appendBytes:&a length:sizeof(UInt8)];
[dataOut appendBytes:&charBuf length:sizeof(unsigned char) * (count - MIN_RUN)];
}

/* determine run length (MIN_RUN so far) */
count = MIN_RUN;
while (true){
if (bytesOffset < bytesLength){
nextChar = bytesIn[bytesOffset];
bytesOffset += skip;
} else {
currIsEOF = YES;
nextChar = EOF;
}
if (nextChar != currChar) break;

count++;
if (count == MAX_RUN){
/* run is at max length */
break;
}
}

/* write out encoded run length and run symbol */
UInt8 a = ((int)(1 - (int)(count)));
[dataOut appendBytes:&a length:sizeof(UInt8)];
[dataOut appendBytes:&currChar length:sizeof(UInt8)];

if ((!currIsEOF) && (count != MAX_RUN)){
/* make run breaker start of next buffer */
charBuf[0] = nextChar;
count = 1;
} else {
/* file or max run ends in a run */
count = 0;
}
}
}

if (count == MAX_READ){
int i;

/* write out buffer */
UInt8 a = MAX_COPY - 1;
[dataOut appendBytes:&a length:sizeof(UInt8)];
[dataOut appendBytes:&charBuf[0] length:sizeof(unsigned char) * MAX_COPY];

/* start a new buffer */
count = MAX_READ - MAX_COPY;

/* copy excess to front of buffer */
for (i = 0; i < count; i++)
charBuf[i] = charBuf[MAX_COPY + i];
}

if (bytesOffset < bytesLength)
currChar = bytesIn[bytesOffset];
else
currIsEOF = YES;
bytesOffset += skip;
}

/* write out last buffer */
if (0 != count){
if (count <= MAX_COPY){
/* write out entire copy buffer */
UInt8 a = count - 1;
[dataOut appendBytes:&a length:sizeof(UInt8)];
[dataOut appendBytes:&charBuf length:sizeof(unsigned char) * count];
}
else
{
/* we read more than the maximum for a single copy buffer */
UInt8 a = MAX_COPY - 1;
[dataOut appendBytes:&a length:sizeof(UInt8)];
[dataOut appendBytes:&charBuf length:sizeof(unsigned char) * MAX_COPY];

/* write out remainder */
count -= MAX_COPY;
a = count - 1;
[dataOut appendBytes:&a length:sizeof(UInt8)];
[dataOut appendBytes:&charBuf[MAX_COPY] length:sizeof(unsigned char) * count];
}
}

return dataOut;
}

@end
[/objc]

]]>
Custom compiler flags in XCode http://www.gotow.net/creative/wordpress/?p=40 2009-04-01T03:39:55Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 40


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
So I’ve been messing with compiler flags in XCode for the last hour or so, and it turns out I was totally misunderstanding things. If you get info on a project or target in XCode, there’s a “User-Defined” section at the bottom that allows you to create your own flags for use at compile time. However, these flags are not actually passed into GCC! (Mistake 1!) I assumed that adding a key-value pair to the list would pass the flag straight through to GCC, but it appears that they are for use in build scripts, etc… To pass a custom flag through to GCC so that you can use it in #ifdef and #if macros at compile time, you have to add a User-Defined Setting called “OTHER_CFLAGS". For my project, I set the value to “-DIS_PHOTO_CHAT=1″. At compile time, the exact text is passed as an argument to GCC. You can set different values in different targets - and I was able to conditionally include some code in a header file using #if (IS_PHOTO_CHAT==1)… It seems like you can use XCode’s built-in flags like ${TARGET_NAME} and ${PRODUCT_NAME} to insert variables into the value of OTHER_CFLAGS, but if your target name has spaces, I think you’re at a loss. I tried to set -DTARGET_NAME=${TARGET_NAME} for about an hour, but the target name had a space and I can’t get GCC to accept the value (tried quotes… no luck…) I’m no command-line-compiler-whiz, so I’m sure there’s a trick, but Google hasn’t turned anything up. I’m still surprised OTHER_CFLAGS wasn’t preset to “” in the target build settings. There’s an empty field for “Other Code Signing Flags” (which seems less useful!) Oh well… Guess XCode is in permanent beta anyway? So I’ve been messing with compiler flags in XCode for the last hour or so, and it turns out I was totally misunderstanding things. If you get info on a project or target in XCode, there’s a “User-Defined” section at the bottom that allows you to create your own flags for use at compile time. However, these flags are not actually passed into GCC! (Mistake 1!) I assumed that adding a key-value pair to the list would pass the flag straight through to GCC, but it appears that they are for use in build scripts, etc…

To pass a custom flag through to GCC so that you can use it in #ifdef and #if macros at compile time, you have to add a User-Defined Setting called “OTHER_CFLAGS". For my project, I set the value to “-DIS_PHOTO_CHAT=1″. At compile time, the exact text is passed as an argument to GCC. You can set different values in different targets - and I was able to conditionally include some code in a header file using #if (IS_PHOTO_CHAT==1)…

It seems like you can use XCode’s built-in flags like ${TARGET_NAME} and ${PRODUCT_NAME} to insert variables into the value of OTHER_CFLAGS, but if your target name has spaces, I think you’re at a loss. I tried to set -DTARGET_NAME=${TARGET_NAME} for about an hour, but the target name had a space and I can’t get GCC to accept the value (tried quotes… no luck…) I’m no command-line-compiler-whiz, so I’m sure there’s a trick, but Google hasn’t turned anything up.

I’m still surprised OTHER_CFLAGS wasn’t preset to “” in the target build settings. There’s an empty field for “Other Code Signing Flags” (which seems less useful!) Oh well… Guess XCode is in permanent beta anyway?

]]>
The Best Wordpress Site Ever? http://www.gotow.net/creative/wordpress/?p=38 2009-03-25T05:53:15Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 38


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
So I accidentally clicked an ad this afternoon and stumbled across Ecoki.com, an online community for eco-friendly folks. I hadn’t even scrolled half way down their home page when I found myself thinking: “What was this built in?” Ecoki is quite possibly the best designed wordpress site I’ve ever seen. I had to look at the page source to figure it out. http://www.ecoki.com/ It looks like it’s a completely custom template. Must have cost a fortune… It seems like there are only four staff authors on the site - so they’ve been pretty busy!

So I accidentally clicked an ad this afternoon and stumbled across Ecoki.com, an online community for eco-friendly folks. I hadn’t even scrolled half way down their home page when I found myself thinking: “What was this built in?” Ecoki is quite possibly the best designed wordpress site I’ve ever seen. I had to look at the page source to figure it out.

http://www.ecoki.com/

It looks like it’s a completely custom template. Must have cost a fortune… It seems like there are only four staff authors on the site - so they’ve been pretty busy!]]> Creating a UIImage from a CGLayer http://www.gotow.net/creative/wordpress/?p=33 2009-02-08T19:22:07Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 33


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
CGLayers are great for drawing - especially when things need to be drawn over and over again. Converting a CGLayer to a UIImage is another story, though. NetSketch uses CGLayers for the drawing canvas, but converts them to UIImages when you go to upload your drawing or email it to a friend. The code below shows how it’s done. The CGLayer is drawn into a bitmap CGContext of the same size, and then a CGImage is created around the CGContext. The CGImage can be turned into a UIImage, and you’re done! Be sure to leave a comment if you find this function useful! [sourcecode language=’c'] UIImage* UIImageFromLayer(CGLayerRef layer) { // Create the bitmap context CGContextRef bitmapContext = NULL; void * bitmapData; int bitmapByteCount; int bitmapBytesPerRow; CGSize size = CGLayerGetSize(layer); // Declare the number of bytes per row. Each pixel in the bitmap in this // example is represented by 4 bytes; 8 bits each of red, green, blue, and // alpha. bitmapBytesPerRow = (size.width * 4); bitmapByteCount = (bitmapBytesPerRow * size.height); // Allocate memory for image data. This is the destination in memory // where any drawing to the bitmap context will be rendered. bitmapData = malloc( bitmapByteCount ); if (bitmapData == NULL) { return nil; } // Create the bitmap context. We want pre-multiplied ARGB, 8-bits // per component. Regardless of what the source image format is // (CMYK, Grayscale, and so on) it will be converted over to the format // specified here by CGBitmapContextCreate. bitmapContext = CGBitmapContextCreate (bitmapData, size.width, size.height,8,bitmapBytesPerRow, CGColorSpaceCreateDeviceRGB(),kCGImageAlphaNoneSkipFirst); if (bitmapContext == NULL) // error creating context return nil; CGContextScaleCTM(bitmapContext, 1, -1); CGContextTranslateCTM(bitmapContext, 0, -size.height); // Draw the image to the bitmap context. Once we draw, the memory // allocated for the context for rendering will then contain the // raw image data in the specified color space. CGContextDrawLayerAtPoint(bitmapContext, CGPointZero, layer); CGImageRef img = CGBitmapContextCreateImage(bitmapContext); UIImage* ui_img = [UIImage imageWithCGImage: img]; CGImageRelease(img); CGContextRelease(bitmapContext); free(bitmapData); return ui_img; } [/sourcecode] CGLayers are great for drawing - especially when things need to be drawn over and over again. Converting a CGLayer to a UIImage is another story, though. NetSketch uses CGLayers for the drawing canvas, but converts them to UIImages when you go to upload your drawing or email it to a friend. The code below shows how it’s done. The CGLayer is drawn into a bitmap CGContext of the same size, and then a CGImage is created around the CGContext. The CGImage can be turned into a UIImage, and you’re done!

Be sure to leave a comment if you find this function useful!

[sourcecode language=’c']
UIImage* UIImageFromLayer(CGLayerRef layer)
{
// Create the bitmap context
CGContextRef bitmapContext = NULL;
void * bitmapData;
int bitmapByteCount;
int bitmapBytesPerRow;
CGSize size = CGLayerGetSize(layer);

// Declare the number of bytes per row. Each pixel in the bitmap in this
// example is represented by 4 bytes; 8 bits each of red, green, blue, and
// alpha.
bitmapBytesPerRow = (size.width * 4);
bitmapByteCount = (bitmapBytesPerRow * size.height);

// Allocate memory for image data. This is the destination in memory
// where any drawing to the bitmap context will be rendered.
bitmapData = malloc( bitmapByteCount );
if (bitmapData == NULL)
{
return nil;
}

// Create the bitmap context. We want pre-multiplied ARGB, 8-bits
// per component. Regardless of what the source image format is
// (CMYK, Grayscale, and so on) it will be converted over to the format
// specified here by CGBitmapContextCreate.
bitmapContext = CGBitmapContextCreate (bitmapData, size.width, size.height,8,bitmapBytesPerRow,
CGColorSpaceCreateDeviceRGB(),kCGImageAlphaNoneSkipFirst);

if (bitmapContext == NULL)
// error creating context
return nil;

CGContextScaleCTM(bitmapContext, 1, -1);
CGContextTranslateCTM(bitmapContext, 0, -size.height);

// Draw the image to the bitmap context. Once we draw, the memory
// allocated for the context for rendering will then contain the
// raw image data in the specified color space.
CGContextDrawLayerAtPoint(bitmapContext, CGPointZero, layer);
CGImageRef img = CGBitmapContextCreateImage(bitmapContext);
UIImage* ui_img = [UIImage imageWithCGImage: img];

CGImageRelease(img);
CGContextRelease(bitmapContext);
free(bitmapData);

return ui_img;

}
[/sourcecode]

]]>
Safari-style iPhone Checkbox http://www.gotow.net/creative/wordpress/?p=31 2009-02-08T19:09:17Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 31


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
I was finishing up work on an iPhone app earlier today and needed to add a check box to the interface. Interface Builder provides a “checkbox-ish” control that slides between “On” and “Off” states but it didn’t fit in well with the rest of the app. I needed a smaller one like the one that Mobile Safari provides for web forms. It was pretty easy to create using a UIButton, but I spent a few minutes putting together images for the different button states. Click the link below to grab ‘em - and leave a comment if you find them useful! iPhone Safari-Style Checkbox.zip I was finishing up work on an iPhone app earlier today and needed to add a check box to the interface. Interface Builder provides a “checkbox-ish” control that slides between “On” and “Off” states but it didn’t fit in well with the rest of the app. I needed a smaller one like the one that Mobile Safari provides for web forms.

It was pretty easy to create using a UIButton, but I spent a few minutes putting together images for the different button states. Click the link below to grab ‘em - and leave a comment if you find them useful!

iPhone Safari-Style Checkbox.zip

]]>
NetSketch mentioned on Digg! http://www.gotow.net/creative/wordpress/?p=28 2009-01-17T16:42:01Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 28


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
Check it out! I was looking at Digg this morning and noticed a post about “Apple iPhone Art.” It points to an article in the Telegraph with a gallery of art done by Steve Sprang, the author of Brushes. The description on Digg mentioned NetSketch, though. Kind of cool to see it on the front page! Check it out! I was looking at Digg this morning and noticed a post about “Apple iPhone Art.” It points to an article in the Telegraph with a gallery of art done by Steve Sprang, the author of Brushes. The description on Digg mentioned NetSketch, though. Kind of cool to see it on the front page!

]]>
A cheap math app - not a good idea? http://www.gotow.net/creative/wordpress/?p=18 2009-01-10T23:43:17Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 18


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
Earlier this month, I put the finishing touches on Mathomatic and published it to the App Store. Mathomatic is a pretty small app for doing symbolic math. I needed it for a class I took in Signal Processing, and I figured it’d be useful to others as well. It expands and simplifies complex polynomials, takes derivatives and integrals, and a couple other cool things. I built it around the Mathomatic CAS, an open source computer algebra system written in C, and spent most of my time creating a beautiful equation renderer for the iPhone (hopefully to be open sourced soon!) I decided to sell it for $1.99 because it seemed like a low price would A) increase sales and B) lead to fewer complaints about the limited feature set (After all - how much can you expect for $1.99?). After selling NetSketch for $5.99, I was curious to see how a low priced app would go. I’ve been considering lowering the price on NetSketch, and I thought it’d help determine the optimal price. Well, it turns out you can expect a lot for $1.99 - at least in the U.S (Interestingly enough, Mathomatic has a much higher rating overseas!). Mathomatic got some bad initial reviews from people looking for more powerful features. The most popular complaints mentioned were: - Only supports the variables X, Y, and Z - No support for trigonometric functions - No in-app help These are all valid complaints, but they were all things I’d hoped to avoid by pricing the app low. The only other app I’ve heard of that offers similar functionality on the iPhone is SpaceTime, and it’s $19.99. It is, of course, a much richer application with more features and most likely some documentation. So the real question is - do the people that purchase math apps for the iPhone care how much they cost? Maybe there’s really no space in the market for a “cheap” math app! Earlier this month, I put the finishing touches on Mathomatic and published it to the App Store. Mathomatic is a pretty small app for doing symbolic math. I needed it for a class I took in Signal Processing, and I figured it’d be useful to others as well. It expands and simplifies complex polynomials, takes derivatives and integrals, and a couple other cool things. I built it around the Mathomatic CAS, an open source computer algebra system written in C, and spent most of my time creating a beautiful equation renderer for the iPhone (hopefully to be open sourced soon!)

I decided to sell it for $1.99 because it seemed like a low price would A) increase sales and B) lead to fewer complaints about the limited feature set (After all - how much can you expect for $1.99?). After selling NetSketch for $5.99, I was curious to see how a low priced app would go. I’ve been considering lowering the price on NetSketch, and I thought it’d help determine the optimal price.

Well, it turns out you can expect a lot for $1.99 - at least in the U.S (Interestingly enough, Mathomatic has a much higher rating overseas!). Mathomatic got some bad initial reviews from people looking for more powerful features. The most popular complaints mentioned were:
- Only supports the variables X, Y, and Z
- No support for trigonometric functions
- No in-app help

These are all valid complaints, but they were all things I’d hoped to avoid by pricing the app low. The only other app I’ve heard of that offers similar functionality on the iPhone is SpaceTime, and it’s $19.99. It is, of course, a much richer application with more features and most likely some documentation.

So the real question is - do the people that purchase math apps for the iPhone care how much they cost? Maybe there’s really no space in the market for a “cheap” math app!

]]>
Custom UIWebView Navigation Controller http://www.gotow.net/creative/wordpress/?p=16 2008-11-25T01:30:17Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 16


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
Download Drill Down Example + Controller Source (1.6MB .zip) The next version of NetSketch will include a community browser, allowing you to view uploaded drawings, watch replays, and leave comments without leaving the app. When I started working on the community interface, I looked to other apps for inspiration. Almost every app I’ve used on the iPhone use a sliding navigation scheme, giving you the feeling that you’re drilling down into content as you use the application. This interface is intuitive in a lot of contexts, and dates back to the original iPod. The Facebook app allows you to browse other people’s Facebook pages and uses a drill down navigation bar. This works well for the social-network space because you can drill down to look at information and then return to the first page quickly. I decided to use a UINavigationBar and implement a similar drill-down interface for the community part of NetSketch. However, I didn’t want to create custom controllers for each page in the community. I wanted to be able to improve the community without updating the app, and I didn’t want to write a communication layer to download and parse images and custom XML from the server. Using a UIWebView seemed like the obvious choice. It could make retrieving content more efficient, and pages could be changed on the fly. With WebKit’s support for custom CSS, I could make the interface look realistic and comprable to a pile of custom-written views. I quickly realized that it wasn’t all that easy to implement “drill down” behavior with a UIWebView. Early on, I ruled out the possibility of creating a mock navigation bar in HTML. Since Safari on the iPhone doesn’t support scrolling iframes or “position:static", “position:fixed” CSS tags, there was no good way to make the bar sit at the top of the screen while allowing the user to scroll. I decided that a native UINavigationBar would be more practical and provide a better user experience. However, the default UINavigationController class was built to use separate controllers for each layer, and doesn’t worry about freeing up memory as the hierarchy grows. Since web pages can be very large, I decided that I should follow suit with Mobile Safari and keep less than eight in memory at once. I tried several solutions, and finally created a custom DrillDownWebController class with a manually managed UINavigationBar to handle the interface. Here’s a video of it in action: Click To Play The DrillDownWebController maintains a “stack” of DrillDownPages, with each page representing a single layer in the drill-down hierarchy. A DrillDownWebController can be a root level controller, or it can be loaded into an existing UINavigationController. When it appears, it silently swaps its parent’s navigation bar with it’s own and loads the first page. The DrillDownPage is a wrapper for a UIWebView that acts as its delegate and provides higher-level access to important properties of the page, such as it’s title. I decided to pull this functionality out of the controller because it reduced confusion when multiple UIWebViews were being loaded. When the user clicks a link in the top level web view, a new DrillDownPage object is created and it begins loading the requested page in an invisible UIWebView. The controller locks the current page and displays an activity indicator in the top right corner of the navigation bar. When loading is complete, it animates a slide to the new page and updates the navigation bar. All the other pages in the page “stack” are notified that their position in the drill-down hierarchy has changed. The notification step is important, because Page objects need to perform cleanup operations if they are far up the hierarchy. To save memory, they take an image of their UIWebView and release it, displaying the image as a placeholder. If the user backs up the hierarchy and the page becomes visible, a progress indicator is displayed and the actual page is reloaded.The DrillDownWebController provides several features in addition to the basic drill-down behavior. Meta tags on the HTML pages can be used to add a button to the top right of the UINavigationBar and program it to load a new URL. Custom url handlers can be used to perform custom code in the controller when the user clicks links in the page. This functionality was used in NetSketch to make netsketch:// links execute custom code and play drawing replays. Also, standard <a> links in a page cause a drill-down animation when the next page is loaded. Form submissions do not cause a drill down. Tiny forms can be used to make some links drill down and others reload the existing page in the hierarchy.I’ve posted source code for the DrillDownWebController as well as an example. I think it will be useful to anyone who wants to implement a drill down interface without writing a whole lot of code. Enjoy! Drill Down Example + Controller Source (1.6MB .zip) Download Drill Down Example
+ Controller Source
(1.6MB .zip)

The next version of NetSketch will include a community browser, allowing you to view uploaded drawings, watch replays, and leave comments without leaving the app. When I started working on the community interface, I looked to other apps for inspiration. Almost every app I’ve used on the iPhone use a sliding navigation scheme, giving you the feeling that you’re drilling down into content as you use the application. This interface is intuitive in a lot of contexts, and dates back to the original iPod. The Facebook app allows you to browse other people’s Facebook pages and uses a drill down navigation bar. This works well for the social-network space because you can drill down to look at information and then return to the first page quickly.

I decided to use a UINavigationBar and implement a similar drill-down interface for the community part of NetSketch. However, I didn’t want to create custom controllers for each page in the community. I wanted to be able to improve the community without updating the app, and I didn’t want to write a communication layer to download and parse images and custom XML from the server.

Using a UIWebView seemed like the obvious choice. It could make retrieving content more efficient, and pages could be changed on the fly. With WebKit’s support for custom CSS, I could make the interface look realistic and comprable to a pile of custom-written views.

I quickly realized that it wasn’t all that easy to implement “drill down” behavior with a UIWebView. Early on, I ruled out the possibility of creating a mock navigation bar in HTML. Since Safari on the iPhone doesn’t support scrolling iframes or “position:static", “position:fixed” CSS tags, there was no good way to make the bar sit at the top of the screen while allowing the user to scroll. I decided that a native UINavigationBar would be more practical and provide a better user experience. However, the default UINavigationController class was built to use separate controllers for each layer, and doesn’t worry about freeing up memory as the hierarchy grows. Since web pages can be very large, I decided that I should follow suit with Mobile Safari and keep less than eight in memory at once.

I tried several solutions, and finally created a custom DrillDownWebController class with a manually managed UINavigationBar to handle the interface. Here’s a video of it in action:

Video thumbnail. Click to play
Click To Play

The DrillDownWebController maintains a “stack” of DrillDownPages, with each page representing a single layer in the drill-down hierarchy. A DrillDownWebController can be a root level controller, or it can be loaded into an existing UINavigationController. When it appears, it silently swaps its parent’s navigation bar with it’s own and loads the first page.

The DrillDownPage is a wrapper for a UIWebView that acts as its delegate and provides higher-level access to important properties of the page, such as it’s title. I decided to pull this functionality out of the controller because it reduced confusion when multiple UIWebViews were being loaded. When the user clicks a link in the top level web view, a new DrillDownPage object is created and it begins loading the requested page in an invisible UIWebView. The controller locks the current page and displays an activity indicator in the top right corner of the navigation bar. When loading is complete, it animates a slide to the new page and updates the navigation bar. All the other pages in the page “stack” are notified that their position in the drill-down hierarchy has changed.

The notification step is important, because Page objects need to perform cleanup operations if they are far up the hierarchy. To save memory, they take an image of their UIWebView and release it, displaying the image as a placeholder. If the user backs up the hierarchy and the page becomes visible, a progress indicator is displayed and the actual page is reloaded.The DrillDownWebController provides several features in addition to the basic drill-down behavior. Meta tags on the HTML pages can be used to add a button to the top right of the UINavigationBar and program it to load a new URL. Custom url handlers can be used to perform custom code in the controller when the user clicks links in the page. This functionality was used in NetSketch to make netsketch:// links execute custom code and play drawing replays.

Also, standard <a> links in a page cause a drill-down animation when the next page is loaded. Form submissions do not cause a drill down. Tiny forms can be used to make some links drill down and others reload the existing page in the hierarchy.I’ve posted source code for the DrillDownWebController as well as an example. I think it will be useful to anyone who wants to implement a drill down interface without writing a whole lot of code.

Enjoy!

Drill Down Example + Controller Source (1.6MB .zip)

]]>
Stopping a Runaway WebView http://www.gotow.net/creative/wordpress/?p=14 2008-11-10T03:35:24Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 14


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
It turns out, all of the WebViews you create on the iPhone are managed by a central object called WebCore. It seems to be some sort of singleton that takes care of loading content and managing resource loading threads - but it can lead to some interesting problems if you try to rapidly create, display, and release a series of WebViews. The upcoming version of NetSketch uses WebViews to display community content, but it wraps the web content in an iPhone native navigation bar. That way, there’s a feeling of “drilling down” into pages in the community. (Think: facebook app). It makes it hard to tell the entire thing is  done with WebViews and you get some more user-friendly behavior (like using the top right button in the navigation bar to login/logout). After several iterations, I chose to implement this “drilldown” approach using a custom navigation bar and separate WebViews for each page. A “navigation stack” holds the WebViews of previous pages, and if you drill down too far, they are converted to UIImages and the actual WebViews are released. This behavior helps limit memory consumption, while allowing you to perform common actions quickly (going back and clicking another thumbnail, for example). Keeping images of very old views allows you to rapidly exit the interface without seeing blank pages or waiting for old pages to reload. Unfortunately, my implementation seems to have run afoul of WebCore. It turns out that if you request a page in a WebView and attempt to release the WebView before it’s done loading, WebCore will attempt to send it delegate messages once it’s been destroyed - and throw exceptions all over the place. My first instinct was to call [webView stopLoading], but that is (apparently) asynchronous and doesn’t actually stop the WebCore from preparing some of the content. If you register an object as a WebView delegate, you’ll notice that you can still recieve webViewDidFinishLoad:(UIWebView *)webView after a stopLoading call. I messed around with this for a while, and was almost ready to create a “ready-to-release” stack for webViews that were still loading. As a last resort, I tried calling [webView loadRequest: nil], and got some promising results. When you call loadRequest, the WebView  makes an asynchronous call to [sourcecode language=’c'] - (BOOL)webView:(UIWebView *)w shouldStartLoadWithRequest:(NSURLRequest *)r navigationType:(UIWebViewNavigationType)navigationType [/sourcecode] When this call returns true, the WebView attempts to load the URL and realizes it is nil. It will almost immediately call - (void)webView:(UIWebView *)w didFailLoadWithError:(NSError *)error, and then it is officially DEAD. You can safetly release the WebView and its delegate without running the risk of further callbacks hitting deallocated objects. This isn’t a perfect solution (read: it’s a ridiculous hack). It makes stopping and releasing a WebView a multi-step asynchronous process. However, unlike the [webView stopLoading] approach, it results in a consistent series of delegate callbacks that you can observe and also appears to work 100% of the time. In my implementation, the navigation controller tells a WebView wrapper object to “unlink” the WebView. It detaches itself from everything and calls loadRequest:nil on the WebView. When the didFailLoadWithError: delegate call is received, the WebView is released, the delegate connection is broken and the wrapper is automatically released as well. So far, so good. But I still wonder - what exactly does stopLoading do? It turns out, all of the WebViews you create on the iPhone are managed by a central object called WebCore. It seems to be some sort of singleton that takes care of loading content and managing resource loading threads - but it can lead to some interesting problems if you try to rapidly create, display, and release a series of WebViews.

The upcoming version of NetSketch uses WebViews to display community content, but it wraps the web content in an iPhone native navigation bar. That way, there’s a feeling of “drilling down” into pages in the community. (Think: facebook app). It makes it hard to tell the entire thing is  done with WebViews and you get some more user-friendly behavior (like using the top right button in the navigation bar to login/logout). After several iterations, I chose to implement this “drilldown” approach using a custom navigation bar and separate WebViews for each page. A “navigation stack” holds the WebViews of previous pages, and if you drill down too far, they are converted to UIImages and the actual WebViews are released. This behavior helps limit memory consumption, while allowing you to perform common actions quickly (going back and clicking another thumbnail, for example). Keeping images of very old views allows you to rapidly exit the interface without seeing blank pages or waiting for old pages to reload.

Unfortunately, my implementation seems to have run afoul of WebCore. It turns out that if you request a page in a WebView and attempt to release the WebView before it’s done loading, WebCore will attempt to send it delegate messages once it’s been destroyed - and throw exceptions all over the place. My first instinct was to call [webView stopLoading], but that is (apparently) asynchronous and doesn’t actually stop the WebCore from preparing some of the content. If you register an object as a WebView delegate, you’ll notice that you can still recieve webViewDidFinishLoad:(UIWebView *)webView after a stopLoading call.

I messed around with this for a while, and was almost ready to create a “ready-to-release” stack for webViews that were still loading. As a last resort, I tried calling [webView loadRequest: nil], and got some promising results. When you call loadRequest, the WebView  makes an asynchronous call to

[sourcecode language=’c']

- (BOOL)webView:(UIWebView *)w shouldStartLoadWithRequest:(NSURLRequest *)r navigationType:(UIWebViewNavigationType)navigationType

[/sourcecode]

When this call returns true, the WebView attempts to load the URL and realizes it is nil. It will almost immediately call - (void)webView:(UIWebView *)w didFailLoadWithError:(NSError *)error, and then it is officially DEAD. You can safetly release the WebView and its delegate without running the risk of further callbacks hitting deallocated objects.

This isn’t a perfect solution (read: it’s a ridiculous hack). It makes stopping and releasing a WebView a multi-step asynchronous process. However, unlike the [webView stopLoading] approach, it results in a consistent series of delegate callbacks that you can observe and also appears to work 100% of the time.

In my implementation, the navigation controller tells a WebView wrapper object to “unlink” the WebView. It detaches itself from everything and calls loadRequest:nil on the WebView. When the didFailLoadWithError: delegate call is received, the WebView is released, the delegate connection is broken and the wrapper is automatically released as well.

So far, so good.

But I still wonder - what exactly does stopLoading do?

]]>
The NDA is gone! http://www.gotow.net/creative/wordpress/?p=13 2008-10-01T19:23:42Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 13


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
Apple has finally removed the NDA from non-beta iPhone software, so the community can finally come out of hiding. I can’t wait to actually - you know - talk to people! I’ve got a few things in the pipes for NetSketch, and hopefully I can post some tutorials and info here. Then this blog might actually be useful… - Ben Apple has finally removed the NDA from non-beta iPhone software, so the community can finally come out of hiding. I can’t wait to actually - you know - talk to people! I’ve got a few things in the pipes for NetSketch, and hopefully I can post some tutorials and info here. Then this blog might actually be useful…

- Ben

]]>
NetSketch drawing on a shirt! http://www.gotow.net/creative/wordpress/?p=12 2008-09-05T20:05:17Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 12


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
l0k1 posted a picture of this t-shirt in the NetSketch forums yesterday - made from a NetSketch drawing by another artist. Pretty cool! It’s nice to see stuff like this get put to use - I wasn’t sure anybody was actually using the “Download to Illustrator” option on the site. Seeing stuff like this really makes it worth going the extra mile for features like that. The shirt looks great! iArtMobile went live earlier this week and it looks pretty cool. There’s a gallery where you can view artwork created on the iPhone. I’m a bit biased, but I think the NetSketch stuff looks the best I discovered another drawing app, Brushes, while I was looking at iArtMobile. I downloaded it and played around - and I think it’s the strongest pixel-based drawing app I’ve seen. It’s ridiculously fast and does textured brushes - something that a lot of people have been asking for in NetSketch. Of course, the tradeoff is that the drawing is only 320x480 and, although you can zoom, you can’t draw in any more detail once you zoom in. Oh well… It looks like they built a custom UIScrollView similar to the one in NetSketch, so the pan/zoom gestures are familiar! It’d be nice if we could standardize on that. Some of the other apps - like No.2. use some truly bizarre controls.

l0k1 posted a picture of this t-shirt in the NetSketch forums yesterday - made from a NetSketch drawing by another artist. Pretty cool! It’s nice to see stuff like this get put to use - I wasn’t sure anybody was actually using the “Download to Illustrator” option on the site. Seeing stuff like this really makes it worth going the extra mile for features like that. The shirt looks great!

iArtMobile went live earlier this week and it looks pretty cool. There’s a gallery where you can view artwork created on the iPhone. I’m a bit biased, but I think the NetSketch stuff looks the best :-)

I discovered another drawing app, Brushes, while I was looking at iArtMobile. I downloaded it and played around - and I think it’s the strongest pixel-based drawing app I’ve seen. It’s ridiculously fast and does textured brushes - something that a lot of people have been asking for in NetSketch. Of course, the tradeoff is that the drawing is only 320x480 and, although you can zoom, you can’t draw in any more detail once you zoom in. Oh well… It looks like they built a custom UIScrollView similar to the one in NetSketch, so the pan/zoom gestures are familiar! It’d be nice if we could standardize on that. Some of the other apps - like No.2. use some truly bizarre controls.

]]>
Finally… http://www.gotow.net/creative/wordpress/?p=11 2008-08-12T04:24:26Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 11


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
I broke a lot of stuff this weekend working on NetSketch. I’m not all that familiar with threading, and I wanted to add some progress indicators for long redraws. I ended up doing it a couple times, but it finally works. (You can cancel it half way through the redraw process and everything). For the longest time it was leaking memory, but it turns out I just forgot to call [pool release] when the thread received a cancel message. Oops Opening a 6MB drawing and immediately interrupting the drawing - no leaks! Finally… I broke a lot of stuff this weekend working on NetSketch. I’m not all that familiar with threading, and I wanted to add some progress indicators for long redraws. I ended up doing it a couple times, but it finally works. (You can cancel it half way through the redraw process and everything). For the longest time it was leaking memory, but it turns out I just forgot to call [pool release] when the thread received a cancel message. Oops :-)

Opening a 6MB drawing and immediately interrupting the drawing - no leaks! Finally…

]]>
Another great review! http://www.gotow.net/creative/wordpress/?p=10 2008-08-08T03:00:36Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 10


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
I did a Google search for NetSketch a couple minutes ago (c’mon - I have to do it every day, right?) and I found this great review: http://www.appleiphoneapps.com/2008/07/review-netsketch/. 4.75 stars out of 5! I’m psyched . It’s a bummer that the App Store pushes traffic off the web into iTunes, because I think people don’t read a whole lot online about the apps they buy. Could be wrong though - and good reviews certainally can’t hurt! I’ve made some pretty huge changes to the app this week, including a few fixes that will give it a nice big performance boost I was using an NSDictionary to store the strokes in memory, and then sorting them by key (a timestamp) when I needed to do a full redraw. The sort was necessary, because strokes coming across the network might be placed out of timestamp order, causing incorrect layering. I scanned through the entire drawing architecture, and it turns out I wasn’t performing key->value lookups in the dictionary very often. When they were being performed, the stroke being requested was usually one of the last few added to the drawing. I converted the NSDictionary to two NSArrays - one of keys and one of values. I decided to keep the entire structure sorted all the time, and wrote a modified binary search function so the most popular key-value lookups were really fast. Should be a good update I did a Google search for NetSketch a couple minutes ago (c’mon - I have to do it every day, right?) and I found this great review: http://www.appleiphoneapps.com/2008/07/review-netsketch/. 4.75 stars out of 5! I’m psyched :-). It’s a bummer that the App Store pushes traffic off the web into iTunes, because I think people don’t read a whole lot online about the apps they buy. Could be wrong though - and good reviews certainally can’t hurt!

I’ve made some pretty huge changes to the app this week, including a few fixes that will give it a nice big performance boost :-) I was using an NSDictionary to store the strokes in memory, and then sorting them by key (a timestamp) when I needed to do a full redraw. The sort was necessary, because strokes coming across the network might be placed out of timestamp order, causing incorrect layering. I scanned through the entire drawing architecture, and it turns out I wasn’t performing key->value lookups in the dictionary very often. When they were being performed, the stroke being requested was usually one of the last few added to the drawing. I converted the NSDictionary to two NSArrays - one of keys and one of values. I decided to keep the entire structure sorted all the time, and wrote a modified binary search function so the most popular key-value lookups were really fast.

Should be a good update :-)

]]>
Maximum brush size in Flash http://www.gotow.net/creative/wordpress/?p=8 2008-08-02T00:27:14Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 8


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
I’ve been having a lot of trouble with Flash recently. This weekend, I discovered a line size limitation that doesn’t seem to be documented anywhere. Unfortunately, my.netsketchapp.com was already live and people’s drawings were being rendered incorrectly. The goal was to create an online Flash viewer that replicated the behavior of NetSketch on the iPod. The iPod app supports infinite pan and zoom, and often it’s not possible to render things to a jpg. It’s just not the same. I wrote a small flash “viewer” that loads an XML version of the drawing and allows you to pan and zoom through the drawing the same way you do on the iPod. Here’s a sample of the Flash viewer: http://my.netsketchapp.com/drawing.php?id=205 The problem is, lineStyle() in ActionScript 2.0 will only let you make lines with a stroke width less than 235. Yes… 235. I have no idea why, but I tested at increments of 5 and after 235 the line just stops getting bigger. In other words, these two statements produce the same result: [sourcecode language=’js’] line.lineStyle(235, color, 100, false, “normal", “round","round", 2); line.lineStyle(300, color, 100, false, “normal", “round","round", 2); [/sourcecode] The fix turned out to be pretty easy. There’s a backend application that converts NetSketch’s binary vector format into XML, and I added another pass to the conversion process that scaled the drawing so all the strokes were thinner than 235pt… I was already doing some processing to fit the drawings within an Illustrator compatible EPS document (maximum size of 16383 x 16383). Still, it’s frustrating that Flash has such odd limitations. I haven’t experimented much with ActionScript 3.0, and I’m hoping that may resolve some of these things… I’ve been having a lot of trouble with Flash recently. This weekend, I discovered a line size limitation that doesn’t seem to be documented anywhere. Unfortunately, my.netsketchapp.com was already live and people’s drawings were being rendered incorrectly. The goal was to create an online Flash viewer that replicated the behavior of NetSketch on the iPod. The iPod app supports infinite pan and zoom, and often it’s not possible to render things to a jpg. It’s just not the same. I wrote a small flash “viewer” that loads an XML version of the drawing and allows you to pan and zoom through the drawing the same way you do on the iPod.

Here’s a sample of the Flash viewer:

http://my.netsketchapp.com/drawing.php?id=205

The problem is, lineStyle() in ActionScript 2.0 will only let you make lines with a stroke width less than 235. Yes… 235. I have no idea why, but I tested at increments of 5 and after 235 the line just stops getting bigger. In other words, these two statements produce the same result:

[sourcecode language=’js’]

line.lineStyle(235, color, 100, false, “normal", “round","round", 2);

line.lineStyle(300, color, 100, false, “normal", “round","round", 2);

[/sourcecode]

The fix turned out to be pretty easy. There’s a backend application that converts NetSketch’s binary vector format into XML, and I added another pass to the conversion process that scaled the drawing so all the strokes were thinner than 235pt… I was already doing some processing to fit the drawings within an Illustrator compatible EPS document (maximum size of 16383 x 16383). Still, it’s frustrating that Flash has such odd limitations. I haven’t experimented much with ActionScript 3.0, and I’m hoping that may resolve some of these things…

]]>
Feedback http://www.gotow.net/creative/wordpress/?p=9 2008-08-02T00:27:07Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 9


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
So it’s week 3 for NetSketch! I got a few awesome reviews on iTunes yesterday that basically made my day: “Quite a good app. Has not crashed yet, which is better than every single other app I have. Well worth it, especially considering how reachable the developer is and how frequently it is getting updated with new features."- Crabpot8 “Their website is a fantastic addition to an already great app. I’m on it almost every day to check out other people’s art.” - Obscurum Somehow, that makes not sleeping for a few weeks totally worth it.  Apple released some sales figures for apps on the store yesterday, and it looks like NetSketch has been running about 60 sales a day for the last week or so. Older data isn’t available (yet?) so I’m not sure how the Facebook ads and initial release went. Guess we’ll see! So it’s week 3 for NetSketch! I got a few awesome reviews on iTunes yesterday that basically made my day:

“Quite a good app. Has not crashed yet, which is better than every single other app I have. Well worth it, especially considering how reachable the developer is and how frequently it is getting updated with new features."- Crabpot8

“Their website is a fantastic addition to an already great app. I’m on it almost every day to check out other people’s art.” - Obscurum

Somehow, that makes not sleeping for a few weeks totally worth it.  Apple released some sales figures for apps on the store yesterday, and it looks like NetSketch has been running about 60 sales a day for the last week or so. Older data isn’t available (yet?) so I’m not sure how the Facebook ads and initial release went. Guess we’ll see!

]]>
Scaling a CGImage http://www.gotow.net/creative/wordpress/?p=7 2008-07-19T22:30:38Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 7


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
I was working on NetSketch last night and ran into an interesting problem. To optimize the display of thumbnail images, I decided to cache them at their display size and not at full size (320x380). It worked out really well and led to a huge performance boost at launch. The allocation of memory is so slow on the iPhone that leaving around a bunch of large images just isn’t an option - especially when they all need to be loaded at the same time. I wrote a convenience function to do the heavy lifting, and I’ve posted the source below. Given a CGImageRef and a desired scale (1.0 being the same), it returns another CGImage of a different scale. Of course, for many scenarios you can just shove the full size image into a UIImage and let UIKit resize the image for you. That approach doesn’t perform anti-aliasing, though - and will give you a slightly more grainy result. [sourcecode language="c"] CGImageRef CreateScaledCGImageFromCGImage(CGImageRef image, float scale) { // Create the bitmap context CGContextRef    context = NULL; void *          bitmapData; int             bitmapByteCount; int             bitmapBytesPerRow; // Get image width, height. We’ll use the entire image. int width = CGImageGetWidth(image) * scale; int height = CGImageGetHeight(image) * scale; // Declare the number of bytes per row. Each pixel in the bitmap in this // example is represented by 4 bytes; 8 bits each of red, green, blue, and // alpha. bitmapBytesPerRow   = (width * 4); bitmapByteCount     = (bitmapBytesPerRow * height); // Allocate memory for image data. This is the destination in memory // where any drawing to the bitmap context will be rendered. bitmapData = malloc( bitmapByteCount ); if (bitmapData == NULL) { return nil; } // Create the bitmap context. We want pre-multiplied ARGB, 8-bits // per component. Regardless of what the source image format is // (CMYK, Grayscale, and so on) it will be converted over to the format // specified here by CGBitmapContextCreate. CGColorSpaceRef colorspace = CGImageGetColorSpace(image); context = CGBitmapContextCreate (bitmapData,width,height,8,bitmapBytesPerRow, colorspace,kCGImageAlphaNoneSkipFirst); CGColorSpaceRelease(colorspace); if (context == NULL) // error creating context return nil; // Draw the image to the bitmap context. Once we draw, the memory // allocated for the context for rendering will then contain the // raw image data in the specified color space. CGContextDrawImage(context, CGRectMake(0,0,width, height), image); CGImageRef imgRef = CGBitmapContextCreateImage(context); CGContextRelease(context); free(bitmapData); return imgRef; } [/sourcecode] I was working on NetSketch last night and ran into an interesting problem. To optimize the display of thumbnail images, I decided to cache them at their display size and not at full size (320x380). It worked out really well and led to a huge performance boost at launch. The allocation of memory is so slow on the iPhone that leaving around a bunch of large images just isn’t an option - especially when they all need to be loaded at the same time.

I wrote a convenience function to do the heavy lifting, and I’ve posted the source below. Given a CGImageRef and a desired scale (1.0 being the same), it returns another CGImage of a different scale. Of course, for many scenarios you can just shove the full size image into a UIImage and let UIKit resize the image for you. That approach doesn’t perform anti-aliasing, though - and will give you a slightly more grainy result.

[sourcecode language="c"]

CGImageRef CreateScaledCGImageFromCGImage(CGImageRef image, float scale)
{
// Create the bitmap context
CGContextRef    context = NULL;
void *          bitmapData;
int             bitmapByteCount;
int             bitmapBytesPerRow;

// Get image width, height. We’ll use the entire image.
int width = CGImageGetWidth(image) * scale;
int height = CGImageGetHeight(image) * scale;

// Declare the number of bytes per row. Each pixel in the bitmap in this
// example is represented by 4 bytes; 8 bits each of red, green, blue, and
// alpha.
bitmapBytesPerRow   = (width * 4);
bitmapByteCount     = (bitmapBytesPerRow * height);

// Allocate memory for image data. This is the destination in memory
// where any drawing to the bitmap context will be rendered.
bitmapData = malloc( bitmapByteCount );
if (bitmapData == NULL)
{
return nil;
}

// Create the bitmap context. We want pre-multiplied ARGB, 8-bits
// per component. Regardless of what the source image format is
// (CMYK, Grayscale, and so on) it will be converted over to the format
// specified here by CGBitmapContextCreate.
CGColorSpaceRef colorspace = CGImageGetColorSpace(image);
context = CGBitmapContextCreate (bitmapData,width,height,8,bitmapBytesPerRow,
colorspace,kCGImageAlphaNoneSkipFirst);
CGColorSpaceRelease(colorspace);

if (context == NULL)
// error creating context
return nil;

// Draw the image to the bitmap context. Once we draw, the memory
// allocated for the context for rendering will then contain the
// raw image data in the specified color space.
CGContextDrawImage(context, CGRectMake(0,0,width, height), image);

CGImageRef imgRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
free(bitmapData);

return imgRef;
}
[/sourcecode]

]]>
NetSketch Approved and CafePress Sucks! http://www.gotow.net/creative/wordpress/?p=6 2008-07-10T00:43:49Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 6


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
Finally! I got an email from Apple today confirming that NetSketch will be available on the App Store this Friday. Let’s hope everything goes well this weekend . Unfortunately, CafePress had to step in and rain on my parade. I ordered a few NetSketch t-shirts after I made the 512x512 version of the logo. I haven’t used CafePress in a few years and I decided to give their black shirts a try. I wish I could say it was a positive experience, but they look terrible. I don’t think it was an error on my part - I didn’t use any sort of drop shadow or blur effects in the design and my images were approx. 2300px across. The white areas of the logo and title came out a middle gray - as if the shirt had been washed 50 times already. Black was clearly visible through the text - even from 10+ feet away. Here’s a closeup: I ordered a white shirt as well. I figured that one would have to come out, but the black parts aren’t solid, and it looks as though it was printed in two passes. There’s quite a bit of horizontal bleeding on the text - as though it was printed once, and then printed again 2mm off. There might be some other reason, but CafePress’ support pages were no help! Either way, I’ll be leaving CafePress. I’ve requested a refund and ordered shirts from Zazzle, which was the recommended on this site: http://www.podentrepreneur.com/2008/02/21/zazzle-the-clear-winner-in-dark-shirt-printing/ We’ll see how it goes! Finally! I got an email from Apple today confirming that NetSketch will be available on the App Store this Friday. Let’s hope everything goes well this weekend :-).

NetSketch Approval Email

Unfortunately, CafePress had to step in and rain on my parade. I ordered a few NetSketch t-shirts after I made the 512x512 version of the logo. I haven’t used CafePress in a few years and I decided to give their black shirts a try. I wish I could say it was a positive experience, but they look terrible. I don’t think it was an error on my part - I didn’t use any sort of drop shadow or blur effects in the design and my images were approx. 2300px across. The white areas of the logo and title came out a middle gray - as if the shirt had been washed 50 times already. Black was clearly visible through the text - even from 10+ feet away. Here’s a closeup:

I ordered a white shirt as well. I figured that one would have to come out, but the black parts aren’t solid, and it looks as though it was printed in two passes. There’s quite a bit of horizontal bleeding on the text - as though it was printed once, and then printed again 2mm off. There might be some other reason, but CafePress’ support pages were no help!

Bad white shirt.

Either way, I’ll be leaving CafePress. I’ve requested a refund and ordered shirts from Zazzle, which was the recommended on this site:
http://www.podentrepreneur.com/2008/02/21/zazzle-the-clear-winner-in-dark-shirt-printing/

We’ll see how it goes!

]]>
NetSketch in final testing http://www.gotow.net/creative/wordpress/?p=5 2008-07-02T23:01:20Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 5


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
I’ve been hard at work on NetSketch these past few weeks and the site is finally up over at http://www.netsketchapp.com/. I’m putting some finishing touches on the app and it should be ready for the App store launch on the 11th! - Ben I’ve been hard at work on NetSketch these past few weeks and the site is finally up over at http://www.netsketchapp.com/. I’m putting some finishing touches on the app and it should be ready for the App store launch on the 11th!

- Ben

]]>
NetSketch is coming… http://www.gotow.net/creative/wordpress/?p=4 2008-05-27T22:35:37Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 4


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
I can’t believe it’s been so long since I posted here. It seems like it’s been a few months since school let out, but it’s only been 4(?) weeks! It’s been a busy summer so far - I’m working full-time for Mailtrust here in Blacksburg - and I’ve been working to put the finishing touches on my iPhone app at night. I’ve finally named it NetSketch. It’s  collaborative drawing software that lets you draw with friends on the iPhone’s screen. It’s on track for release after Apple’s June 9th rev of iPhone O 2.0! NetSketch Website It’s not done yet and I’ll probably save the real version until the app is done. Stay tuned, though! I can’t believe it’s been so long since I posted here. It seems like it’s been a few months since school let out, but it’s only been 4(?) weeks! It’s been a busy summer so far - I’m working full-time for Mailtrust here in Blacksburg - and I’ve been working to put the finishing touches on my iPhone app at night. I’ve finally named it NetSketch. It’s  collaborative drawing software that lets you draw with friends on the iPhone’s screen. It’s on track for release after Apple’s June 9th rev of iPhone O 2.0!

NetSketch Website

It’s not done yet and I’ll probably save the real version until the app is done. Stay tuned, though!

]]>
Document Fingerprinting in MATLAB http://www.gotow.net/creative/wordpress/?p=3 2008-02-28T08:00:06Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 3


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
It’s been a while since I’ve posted here. The semester is in full swing, and things at the newspaper have been keeping me busy. A few weeks ago I started reading about document fingerprinting for plagiarism detection, and I’ve made some progress.I was hoping to create an online lyrics aggregator and software to build a lyrics database. Basically, server software would search google for “Better than Ezra Closer Lyrics", visit the top pages, and use plagiarism detection algorithms to “find” the lyrics on the pages. The basic assumption was that sections of the HTML containing lyrics would be nearly identical. Taking this area of each page, the data could be further refined by identifying phrases and punctuation that was inconsistent across the results. I hate going to sites that offer incorrect, damaged lyrics. Lookup lyrics for any rap song and you’ll see the problem. Every site is slightly different. Using plagiarism and document fingerprinting, it would be possible to find the “average” of these versions - hopefully coming to a more accurate version.Since the document fingerprinting / plagiarism detection algorithms were central to the idea, I started looking into them first. An hour of Google searching turned up some interesting results. Namely, I found this paper:  Winnowing: a Document Fingerprinting Algorithm    “Among digital data, documents are the easiest to copy and remove any signatures or fingerprints embedded, which make the pirating the hardest to detect. Anyone can just retype a document or copy a part of it. Document fingerprinting is concerned with accurately identifying and copying, including small partial copies, within large sets of documents.” [From the Abstract]  It does a great job of explaining the idea behind document fingerprinting, the different approaches, and their usefulness in different contexts. So be sure to check it out.I wrote several MATLAB functions to test the algorithms in the paper. After taking Image Processing last semester, I’m pretty comfortable implementing stuff like this. So here’s what it boils down to: %@author: Ben Gotow %@project: Document Fingerprinting %@date: 2/28/08 %@Feel free to reproduce this code in any way. %This function takes a character array as an argument. This implementation %generates kgrams of a constant size (13) and then converts them to hashes. %The hashes are in turn compressed into “windows” of (5) hashes. The %compression approach may be changed. This version returns the lowest value %from the hashes in the window. This method was dicussed in %’Winnowing, a Document Fingerprinting Algorithm’. function f = generateFingerprints(chars) kgram_size = 13; s = 1; [t char_count] = size(chars) k_count = char_count - kgram_size + 1; %parse into k-grams of ascii values kgrams = zeros(k_count,kgram_size); for s = 1:k_count kgrams(s,:) = chars(s:(s+kgram_size-1)); end %convert k-grams into hashes b = 6; s = 1; khash = 0; for i = 1:kgram_size khash = khash + kgrams(s,i)*b^(kgram_size-i); end for s = 2:k_count khash = (khash - kgrams(s-1,1)*b^(kgram_size-1) )*b + kgrams(s,kgram_size); khashes(s) = khash; end %create windows of hashes of length W w_size = 5; %find minimums in windows. in case of repeated minimum values, choose the %rightmost one. Also, store the global location of the min hash. prev_min_position = -1; f_count = 0; for position = 1:(k_count - (w_size-1)) w = khashes(position:(position+w_size-1)); m = min(w); if (m ~= 0) for i = w_size:-1:1 if (w(i) == m) %we have the minimum value. is it the same as the one in the %previous window? check if the position is the same if (prev_min_position ~= position) prev_min_position = position; f_count = f_count + 1; f(f_count,1) = m; f(f_count,2) = position; end break end end end end end It’s been a while since I’ve posted here. The semester is in full swing, and things at the newspaper have been keeping me busy. A few weeks ago I started reading about document fingerprinting for plagiarism detection, and I’ve made some progress.I was hoping to create an online lyrics aggregator and software to build a lyrics database. Basically, server software would search google for “Better than Ezra Closer Lyrics", visit the top pages, and use plagiarism detection algorithms to “find” the lyrics on the pages. The basic assumption was that sections of the HTML containing lyrics would be nearly identical. Taking this area of each page, the data could be further refined by identifying phrases and punctuation that was inconsistent across the results. I hate going to sites that offer incorrect, damaged lyrics. Lookup lyrics for any rap song and you’ll see the problem. Every site is slightly different. Using plagiarism and document fingerprinting, it would be possible to find the “average” of these versions - hopefully coming to a more accurate version.Since the document fingerprinting / plagiarism detection algorithms were central to the idea, I started looking into them first. An hour of Google searching turned up some interesting results. Namely, I found this paper:
 Winnowing: a Document Fingerprinting Algorithm   

“Among digital data, documents are the easiest to copy and remove any signatures or fingerprints embedded, which make the pirating the hardest to detect. Anyone can just retype a document or copy a part of it. Document fingerprinting is concerned with accurately identifying and copying, including small partial copies, within large sets of documents.” [From the Abstract]

 It does a great job of explaining the idea behind document fingerprinting, the different approaches, and their usefulness in different contexts. So be sure to check it out.I wrote several MATLAB functions to test the algorithms in the paper. After taking Image Processing last semester, I’m pretty comfortable implementing stuff like this. So here’s what it boils down to:
%@author: Ben Gotow
%@project: Document Fingerprinting
%@date: 2/28/08

%@Feel free to reproduce this code in any way.

%This function takes a character array as an argument. This implementation %generates kgrams of a constant size (13) and then converts them to hashes. %The hashes are in turn compressed into “windows” of (5) hashes. The %compression approach may be changed. This version returns the lowest value %from the hashes in the window. This method was dicussed in %’Winnowing, a Document Fingerprinting Algorithm’.

function f = generateFingerprints(chars)

kgram_size = 13; s = 1;

[t char_count] = size(chars) k_count = char_count - kgram_size + 1;

%parse into k-grams of ascii values kgrams = zeros(k_count,kgram_size);

for s = 1:k_count kgrams(s,:) = chars(s:(s+kgram_size-1)); end

%convert k-grams into hashes b = 6;

s = 1; khash = 0; for i = 1:kgram_size khash = khash + kgrams(s,i)*b^(kgram_size-i); end

for s = 2:k_count khash = (khash - kgrams(s-1,1)*b^(kgram_size-1) )*b + kgrams(s,kgram_size); khashes(s) = khash; end

%create windows of hashes of length W

w_size = 5;

%find minimums in windows. in case of repeated minimum values, choose the %rightmost one. Also, store the global location of the min hash.

prev_min_position = -1; f_count = 0;

for position = 1:(k_count - (w_size-1)) w = khashes(position:(position+w_size-1)); m = min(w); if (m ~= 0) for i = w_size:-1:1 if (w(i) == m)

%we have the minimum value. is it the same as the one in the %previous window? check if the position is the same

if (prev_min_position ~= position) prev_min_position = position;

f_count = f_count + 1; f(f_count,1) = m; f(f_count,2) = position; end

break end end end end

end

]]>
New resolutions, new design! http://www.gotow.net/creative/wordpress/?p=1 2007-12-29T17:28:34Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 1


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
It’s been a few years since Gotow.net received a full overhaul, and the new site is finally here! I’m pretty excited - there was nothing wrong with the old design, but I was starting to look dated. I’ve been working on the new stuff for a few months now, and I think its looking better than ever. There’s still a bit left to do, and not too much has changed content-wise, yet. 2008 should be a good year, though, so stay tuned! I’ll be posting a lot of the source for the new site here on the blog. I put a lot of work into the media viewer used on the work pages, so expect a downloadable version of that soon!  It’s been a few years since Gotow.net received a full overhaul, and the new site is finally here! I’m pretty excited - there was nothing wrong with the old design, but I was starting to look dated. I’ve been working on the new stuff for a few months now, and I think its looking better than ever. There’s still a bit left to do, and not too much has changed content-wise, yet. 2008 should be a good year, though, so stay tuned!

I’ll be posting a lot of the source for the new site here on the blog. I put a lot of work into the media viewer used on the work pages, so expect a downloadable version of that soon! 

]]>
About http://www.gotow.net/creative/wordpress/?p=2 2007-12-29T17:28:34Z (mailto:bengotow@gmail.com)

Database error: [Table 'gotow_ben.blog_categories' doesn't exist]
SELECT category_id, cat_name, category_nicename, category_description, category_parent FROM blog_categories, blog_post2cat WHERE blog_post2cat.category_id = cat_ID AND blog_post2cat.post_id = 2


Warning: Invalid argument supplied for foreach() in /usr/www/users/gotow/gotow.net/blog/wp-includes/template-functions-category.php on line 114
This is an example of a WordPress page, you could edit this to put information about yourself or your site so readers know where you are coming from. You can create as many pages like this one or sub-pages as you like and manage all of your content inside of WordPress. This is an example of a WordPress page, you could edit this to put information about yourself or your site so readers know where you are coming from. You can create as many pages like this one or sub-pages as you like and manage all of your content inside of WordPress.

]]>