i’m so full of ideas

Archive for March, 2009

iPhone: UIImage rotation and mirroring

March 30, 2009 2:19 am

Update: This post has been superseded. Please go read this instead: UIImage rotation and scaling.

My access logs show that you webbernauts like my iPhone articles better than anything else on this site. Very well then, here’s another one.

I recently found myself with a need to rotate the contents of a UIImage 90 degrees to the right or left and then display it. Apparently this is easy if your image is inside a UIImageView and you’re willing to rotate the whole view, but that’s not what I have in mind. Rotating the UIImage itself turns out to be pretty danged complicated, and requires a great deal of math. I suck at math.

Fortunately for me, the code given in this guy’s blog post tackles 95 percent of the problem, and 100 percent of the math. Heh! It’s not quite what I want, however. The function given in that blog post queries the EXIF data inside the image and rotates it based on that. I want the method’s caller to be able to specify what type of rotation should be done. I tinkered with the code for awhile and got exactly what I wanted.

The original blog post’s code was written as a standalone C function. I wrote my version as a category attached to UIImage. Say you’ve got a UIImage you want to rotate 90 degrees left. It’s done like this:

    newImage = [oldImage rotate:UIImageOrientationLeft];

newImage will be a newly-created copy of oldImage, rotated 90 degrees left. Other options include UIImageOrientationRight, UIImageOrientationDown (for a new image that’s an upside-down copy of the original), and so on. There are also “mirrored” variants, which both rotate the image and mirror its contents left-to-right.

In the process of getting it to work the way I wanted, I made a bunch of changes to the code. I reformatted it so that the lines are no longer than 78 characters. (One of these days I’m going to write a blog post about why I think that’s a good thing.) The original function had two or more unnecessary copies of some data and superfluous calls to external functions, which I eliminated. The old code trimmed the size of the original image if it was bigger than a certain size, which I eliminated. The original had several copies of an identical clause for swapping the width and height of a CGRect, which I refactored into a separate helper function. There are other changes as well, but I’ll spare you the details.

Okay, one more change that I’m going to document. I am 85 percent sure that the original code has a bug. It does not work properly when the input orientation is UIImageOrientationLeftMirrored or UIImageOrientationRightMirrored. The new image gets partially chopped off along one edge, which I fixed. It could be that it was not a bug in the context where the original code was being used, but it definitely is in this new context. I’ve tested all orientations in the iPhone simulator, and they all work.

First, here’s UKImage.h, which defines the category.

// UKImage.h -- extra UIImage methods // by allen brunson  march 29 2009 #ifndef UKIMAGE_H #define UKIMAGE_H #import <UIKit/UIKit.h> @interface UIImage (UKImage) -(UIImage*)rotate:(UIImageOrientation)orient; @end #endif  // UKIMAGE_H

Now here’s UKImage.mm, which defines the rotate: method. You can rename this file to UKImage.m if you never use any C++ constructs in your code.

// UKImage.mm -- extra UIImage methods // by allen brunson  march 29 2009 // based on original code by Kevin Lohman: // http://blog.logichigh.com/2008/06/05/uiimage-fix/ #include "UKImage.h" static CGRect swapWidthAndHeight(CGRect rect) {     CGFloat  swap = rect.size.width;         rect.size.width  = rect.size.height;     rect.size.height = swap;         return rect; } @implementation UIImage (UKImage) -(UIImage*)rotate:(UIImageOrientation)orient {     CGRect             bnds = CGRectZero;     UIImage*           copy = nil;     CGContextRef       ctxt = nil;     CGImageRef         imag = self.CGImage;     CGRect             rect = CGRectZero;     CGAffineTransform  tran = CGAffineTransformIdentity;     rect.size.width  = CGImageGetWidth(imag);     rect.size.height = CGImageGetHeight(imag);         bnds = rect;         switch (orient)     {         case UIImageOrientationUp:         // would get you an exact copy of the original         assert(false);         return nil;                 case UIImageOrientationUpMirrored:         tran = CGAffineTransformMakeTranslation(rect.size.width, 0.0);         tran = CGAffineTransformScale(tran, -1.0, 1.0);         break;         case UIImageOrientationDown:         tran = CGAffineTransformMakeTranslation(rect.size.width,          rect.size.height);         tran = CGAffineTransformRotate(tran, M_PI);         break;         case UIImageOrientationDownMirrored:         tran = CGAffineTransformMakeTranslation(0.0, rect.size.height);         tran = CGAffineTransformScale(tran, 1.0, -1.0);         break;         case UIImageOrientationLeft:         bnds = swapWidthAndHeight(bnds);         tran = CGAffineTransformMakeTranslation(0.0, rect.size.width);         tran = CGAffineTransformRotate(tran, 3.0 * M_PI / 2.0);         break;         case UIImageOrientationLeftMirrored:         bnds = swapWidthAndHeight(bnds);         tran = CGAffineTransformMakeTranslation(rect.size.height,          rect.size.width);         tran = CGAffineTransformScale(tran, -1.0, 1.0);         tran = CGAffineTransformRotate(tran, 3.0 * M_PI / 2.0);         break;         case UIImageOrientationRight:         bnds = swapWidthAndHeight(bnds);         tran = CGAffineTransformMakeTranslation(rect.size.height, 0.0);         tran = CGAffineTransformRotate(tran, M_PI / 2.0);         break;         case UIImageOrientationRightMirrored:         bnds = swapWidthAndHeight(bnds);         tran = CGAffineTransformMakeScale(-1.0, 1.0);         tran = CGAffineTransformRotate(tran, M_PI / 2.0);         break;         default:         // orientation value supplied is invalid         assert(false);         return nil;     }     UIGraphicsBeginImageContext(bnds.size);     ctxt = UIGraphicsGetCurrentContext();     switch (orient)     {         case UIImageOrientationLeft:         case UIImageOrientationLeftMirrored:         case UIImageOrientationRight:         case UIImageOrientationRightMirrored:         CGContextScaleCTM(ctxt, -1.0, 1.0);         CGContextTranslateCTM(ctxt, -rect.size.height, 0.0);         break;                 default:         CGContextScaleCTM(ctxt, 1.0, -1.0);         CGContextTranslateCTM(ctxt, 0.0, -rect.size.height);         break;     }     CGContextConcatCTM(ctxt, tran);     CGContextDrawImage(UIGraphicsGetCurrentContext(), rect, imag);         copy = UIGraphicsGetImageFromCurrentImageContext();     UIGraphicsEndImageContext();     return copy; } @end

newspocalypse y2k9

March 22, 2009 6:53 am

A friend sent me a link to a Red Meat comic recently. I used to read that strip religiously, but I abandoned it about a decade ago. Which is how I came to read Max Cannon’s desperate screed about the shrinking of “local alt weeklies,” as he calls them.

I feel for the guy, as well as the many other cartoonists who he says are in the same boat. I do! Their world is being rent asunder beneath their feet. Things will probably get worse before they get better. But I tell you the one damn thing that absolutely will not work: having your readers wage a letter-writing campaign to the editors. These institutions are fighting for their very existence. You and your devoted readers are barely a blip on their panicked radar.

It’s not just local alt weeklies with a problem. Earlier this month, the Rocky Mountain News went out of business. It was 150 years old. Currently the largest newspaper forced out of business by the ongoing economic recession, but it probably won’t hold that record for long. If the Rocky Mountain News had carried Red Meat, do you think they would have given a rat’s whisker about Max Cannon’s predicament? Ha ha ha.

I used to be pretty worried about this. Blogs are great and all, but which of them is going to have the money to send reporters to Iraq to cover what’s going on over there? The newspapers seem to be trying this tactic as well: “You will miss us when we’re gone!”

Despite feeling bad for all of you in that predicament: actually, no. I won’t miss you. In fact, I wish you’d hurry up and fail more quickly. The sooner you are gone and out of the way, the sooner alternatives can sprout into your former space.

Recently I read two really good articles that changed my thinking on this topic. First: Newspapers and Thinking the Unthinkable, an almost unbelievably good article by the always-insightful Clay Shirky. If you had to boil it down to one short concept, it would be this: “Society doesn’t need newspapers. What we need is journalism.” And then he explains how we’re going to get there. Second: Old Growth Media and the Future of News, by Steven Berlin Johnson. I’d never heard of this guy before, but this article was good enough for me to put him into my RSS reader.

Max Cannon’s screed explains that he doesn’t put strips on his website to make money. He’s just doing it out of the goodness of his heart for his fans. The newspapers are his bread and butter. In that case, Max, it’s time to change your business model. Married to the Sea is a screamingly funny webcomic created by a married couple named Drew and Natalie. They support themselves and their infant child with income from merchandise they sell on the strength of their multiple humor websites.

Despite me not liking his strip that much anymore, I’m sure Max Cannon has his fans. He’s been drawing it for over 20 years now. One would hope that he’s at least as resourceful as all these young whippersnappers who are finding a way to make money off this newfangled “internet” thing? Surely.

iPhone: using UITextField with the virtual keyboard

March 5, 2009 12:27 pm
keyboardscroll main window

keyboardscroll main window

UPDATE: this blog post is now obsolete. I wrote it before I knew enough about the subject to be pontificating on it. Please read my newer blog post on this subject instead.

Now I feel bad. In the last two days, about ten people found their way to my blog by using search terms that suggest they are having as much trouble with the iPhone keyboard as I did. I spent a whole day figuring it out. Then I wrote a blog entry where I just bitched about it, rather than presenting an actual solution. Sorry for wasting your time, folks. Today I give you an example Xcode project that demonstrates how to code for the iPhone’s virtual keyboard, as an act of atonement.

I got most of this stuff from UICatalog, Apple’s sample project that demonstrates the use of most UIKit controls. Some of it didn’t work very well for me, so I modified it a bit. For example, UICatalog not only scrolls the view up to make room for the keyboard, it also shortens the view at the top by the appropriate number of pixels. That caused unpleasant side effects for me, so I did away with that part.

UICatalog was difficult for me to follow, because they’re trying to demonstrate so much stuff at once. It took me awhile to figure out what parts were relevant to my situation. My keyboardscroll project is quite small, so it should be easier for you.

As mentioned previously, I don’t like Interface Builder. Therefore, this project is “nibless,” completely free of XIBs and NIBs. That shiz be stanky, yo. So you could also consider this project to be an example of how to lay out your views entirely in code, if that’s something that interests you.

The keyboardscroll app presents an interface that looks similar to the iPhone’s SMS app. The top part of the window is taken up by a UITextView full of scrolling text. There is a UITextField at the bottom of the window, where the user can type in new text. When the user presses the “Send” button, the text typed is added to the UITextView at the top.

It is impossible to do iPhone programming without making use of magic pixel counts. To keep the damage to a minimum, I put all the magic numbers at the top of ViewController.m and copiously documented them. Most mysterious of all is how many pixels to scroll up when the keyboard appears. Every time I do this, I need to use a different pixel count. It seems like Apple could give us some help here. Perhaps a method to call that would tell you how far up your view has to go to avoid the keyboard.

iphone newbie: view coords, UITextField

March 3, 2009 7:49 am

View coordinates for iPhone

I count three people in my access logs who got here by searching for iPhone programming material, two who are trying to create nibless apps. Rock on, fellow Interface Builder haterz!

iPhone programming is typically very similar to Mac programming. One big change I’ve noticed is that they’ve moved the origin for view coordinates. For NSView on the Mac, the origin point is at the lower left corner, Y coords get bigger as you move up the window. That always seemed deeply, profoundly wrong to me. I guess I’m not the only one who feels that way, because on the iPhone, the origin point is now in the upper left corner, with Y coords getting bigger as you move down the view.

To make matters worse on the Mac, it’s possible to have an NSView with “flipped” coords. In that mode, the origin point is at the top left, as god intended. Some of the stock views are like this, I think NSTableView is one of them. Those views will go crazy if you try to set them back to the “normal” Mac way, displaying their contents incorrectly. What a mess. Looks like iPhone/UIView doesn’t have the “flipped” concept, so everything’s cool again.

View coordinates for Mac

Well, except for other annoyances. Today’s culprit: UITextField. This is the standard one-line text input control for the user to type into, analogous to the Mac’s NSTextField. Way, WAY more complicated than it should be. Every single thing that happens to it in its lifetime, you have to write custom code for.

Creating a UITextField and adding it to a parent view is a trial-and-error affair, sprinkled with magic pixel counts like 30.0 and 4.0 and so on. It’s not just me, that’s the way they do things in Apple’s UICatalog sample app, which shows you how to create most control types. This is bad. What if the dimensions of the control change in the next iPhone software update?

When the user touches the control, the onscreen keyboard pops up. Surprise! It will almost certainly cover the UITextField the user is typing into, so they can’t see what they’re doing. The next step is that you have to write code to scroll your view up to get out of the keyboard’s way when it appears, then scroll the view back down when the keyboard disappears. Why isn’t the iPhone OS doing this for me? Madness!

Now you must add code to your view controller’s viewWillAppear: method, to catch notifications for when the keyboard appears and disappears. Then add more code to viewWillDisappear: to disable those notifications. Then write a method that gets called when the keyboard appears or disappears, to scroll your view up or down by a magic number of pixels. UICatalog hard-codes the value 150.0, which is I suppose the height of the onscreen keyboard. That wasn’t quite right for me, I discovered I needed it to be 166.0. I’m sure that will be the right value to use always and forevermore, right Apple? Sheesh.

Finally, the keyboard won’t ever go away unless you write more code to dismiss it. You must make your view controller the text control’s delegate and write a textFieldShouldReturn: method so you can do something with the Enter key and tell the keyboard to go away. Six new methods later, your quest is at an end. Until the next time you need a UITextField.