Given a UIImage of any dimension, I wish to generate a square “icon” sized version, px pixels to a side, without any distortion (stretching). However, I’m running into a little snag. Not quite sure where the problem is. Here’s what I’m doing so far.
First, given a UImage size, I determine three things: the ratio to use when scaling down the image; a delta (the difference between our desired icon size and the longest side), and an offset (which is used to figure out our origin coordinate when clipping the image):
if (size.width > size.height) {
ratio = px / size.width;
delta = (ratio*size.width - ratio*size.height);
offset = CGPointMake(delta/2, 0);
} else {
ratio = px / size.height;
delta = (ratio*size.height - ratio*size.width);
offset = CGPointMake(0, delta/2);
}
Now, let’s say you have an image 640px wide by 480px high, and we want to get a 50px x 50px icon out of this. The width is greater than the height, so our calculations are:
ratio = 50px / 640px = 0.078125
delta = (ratio * 640px) - (ratio * 480px) = 50px - 37.5px = 12.5px
offset = {x=6.25, y=0}
Next, I create a CGRect rect that is large enough to be cropped down to our desired icon size without distortion, plus a clipRect for clipping purposes:
CGRect rect = CGRectMake(0.0, 0.0, (ratio * size.width) + delta,
(ratio * size.height) + delta);
CGRect clipRect = CGRectMake(offset.x, offset.y, px, px);
Substituting our values from above, we get:
rect = origin {x=0.0, y=0.0}, size {width=62.5, height=50.0}
clipRect = origin {x=6.25, y=0}, size {width=50.0, height=50.0}
So now we have a 62.5px wide by 50px high rect to work with, and a clipping rectangle that grabs the “middle” 50×50 portion.
On to the home stretch! Next, we set up our image context, draw the UIImage (called myImage here) into the rect, set the clipping rectangle, get the (presumably now-clipped) image, use it, and finally clean up our image context:
UIGraphicsBeginImageContext(rect.size);
[myImage drawInRect:rect];
UIRectClip(clipRect);
UIImage *icon = UIGraphicsGetImageFromCurrentImageContext();
// Do something with the icon here ...
UIGraphicsEndImageContext();
Only one problem: The clipping never occurs! I end up with an image 63px wide x 50px high. :(
Perhaps I’m misusing/misunderstanding UIRectClip? I’ve tried shuffling various things around: swapping the use of rect and clipRect, moving UIRectClip before drawInRect:. No dice.
I tried searching for an example of this method online as well, to no avail. For the record, UIRectClip is defined as:
Modifies the current clipping path by
intersecting it with the specified
rectangle.
Shuffling things around gets us a little bit closer:
UIGraphicsBeginImageContext(clipRect.size);
UIRectClip(rect);
[myImage drawInRect:rect];
Now we don’t have distortion, but the clipped image isn’t centered on the original as I expected. Still, at least the image is 50×50, though the variable names are now fouled up as a result of said shuffling. (I’ll respectfully leave renaming as an exercise for the reader.)
Eureka! I had things a little mixed up. This works:
No more need for
rect. The trick appears to be using a negative offset in the clipping rectangle, thereby lining up the origin of where we want to grab our 50 x 50 image (in this example).Perhaps there’s an easier way. If so, please weigh in!