Showing posts with label Categories. Show all posts
Showing posts with label Categories. Show all posts

Monday, November 29, 2010

Drawing Part of a UIImage

Happy Thanksgiving, and sorry for the relative lack of posts here lately. Things are crazier than ever and it's been a challenge finding time to shower, let alone blog.

I do have something to share, today, though. No, it's not the next chapter of OpenGL ES 2.0 for iOS. It's a category that some of you may find useful: a method that allows you to draw only part of a UIImage rather than the entire thing.

On the Mac, NSImage has a handy instance method called drawInRect:fromRect:operation:fraction: that lets you specify exactly which part of an image to draw. On UIImage, we've only got the ability to draw the entire image either unless we drop down to Core Graphics calls. We don't have a nice, easy, convenient way using just UIImage to draw a portion of the image it represents.

I needed this ability in an application I'm working on, so I hacked out the following category. At first glance, this may look to be inefficient, since we're making a copy of the instance's backing CGImage in order to create the sub-image, however I believe that CGImageCreateWithImageInRect() references the original image's bitmap data. I haven't confirmed that it doesn't make a copy of the bitmap data, but the documentation certainly seems to imply it. Anyone know?

Anyway, here is the category; I've even commented the code more pedantically than is normal for me in case anyone might be confused about what's going on. Improvements and bug fixes are, as always, welcome.

@implementation UIImage(MCDrawSubImage)
- (void)drawInRect:(CGRect)drawRect fromRect:(CGRect)fromRect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha
{
CGImageRef drawImage = CGImageCreateWithImageInRect(self.CGImage, fromRect);
if (drawImage != NULL)
{
CGContextRef context = UIGraphicsGetCurrentContext();

// Push current graphics state so we can restore later
CGContextSaveGState(context);

// Set the alpha and blend based on passed in settings
CGContextSetBlendMode(context, blendMode);
CGContextSetAlpha(context, alpha);

// Take care of Y-axis inversion problem by translating the context on the y axis
CGContextTranslateCTM(context, 0, drawRect.origin.y + fromRect.size.height);

// Scaling -1.0 on y-axis to flip
CGContextScaleCTM(context, 1.0, -1.0);

// Then accommodate the translate by adjusting the draw rect
drawRect.origin.y = 0.0f;

// Draw the image
CGContextDrawImage(context, drawRect, drawImage);

// Clean up memory and restore previous state
CGImageRelease(drawImage);

// Restore previous graphics state to what it was before we tweaked it
CGContextRestoreGState(context);
}

}

@end

Tuesday, August 24, 2010

UIImage-Blur

Many moons ago, I wrote convolution kernel for Cocoa. It had convenience class functions to do many different types of filters, including blurring, embossing, outlining, edge detection, horizontal shift, LaPlacian, soften, high pass, etc. Now, this was before Core Image and long before the switch to Intel. I don't remember exactly when I first wrote it, but I'm guessing it was around 2001 or 2002. The method that actually applied the filter to an image used AltiVec if it was available, and if it wasn't, it did a brute force filter on the CPU.

Of course, once the switch to Intel happened, the AltiVec code was no longer was helpful, and then Apple came out with Core Image which includesda convolution kernel and all of the filter settings I had created and more. So, I stopped maintaining the code.

Then, when the iPhone came out and didn't have Accelerate or Core Image, I saw a potential use for the old source code. I had a project early on where I needed to be able to blur an image. So, I blew the dust off the old code. I didn't convert the entire convolution kernel – I didn't want to go through the effort if it wasn't going to work — so I created a blur category on UIImage. And it didn't work.

Pressed for time, I found another solution because I was uncertain the processor on the original iPhone would be able to apply a convolution kernel fast enough for my purposes, but I included the broken code when I released the source code for replicating the Urban Spoon effect. Today, I received an e-mail from a reader who figured out my rather boneheaded mistake. The convolution filter was fine, I just specified the wrong CGImage when specifying my provider while converting the byte data back to a CGImage.

Now, since I wrote that code, Apple has come out with the Accelerate framework for iOS, so it could definitely be made faster. It's unlikely that I will be able to spend the time converting it to use Accelerate unless I need a convolution kernel for my own client work; I've got too much on my plate right now to tackle it. If anyone's interested in doing the full convolution kernel port, you can check out the source code to Crimson FX. It's an old Cocoa project which may not work anymore, but it has, I believe, the last version of the convolution kernel before I gave up maintaining it. It shouldn't be hard to port the entire convolution kernel to iOS in the same manner. Once you get to the underlying byte data, the process is exactly 100% the same (even if byte order is different), and the code to convert to and from the byte data is this UIImage-Blur category.

So, without further ado, I've created a small scaffold project to hold the unaccelerated UIImage-Blur category. Have fun with it and let me know if you use it in anything interesting. If you improve it and would like to share your improvements, let me know, and I'll post it here.

You can find the source code right here. Here's a screenshot of the test scaffold with the original image and after several blurs have been applied. The image is from the Library of Congress Prints and Photographs Collection. Plattsburgh is where I grew up, so this public domain image struck my fancy. I don't know why, but the Army Base was spelled Plattsburg without the ending 'h' even though the city has always been Plattsburgh with the ending 'h'.

Screen shot 2010-08-24 at 10.18.58 AM.png

Thanks to Anthony Gonsalves for finding my error!

Thursday, November 19, 2009

Update to the MPMediaItemCollection Category

While testing and debugging, I made a few tweaks to this category. I still haven't added reordering, but I think the methods that are here are now pretty solid. If you downloaded the earlier one, you probably want to grab the updated version, as it now codes defensively so problems aren't caused when a collection becomes empty by deletion (media item collections can't be created without at least one media item).

MPMediaItemCollection-Utils.h
#import <Foundation/Foundation.h>
#import <MediaPlayer/MediaPlayer.h>

@interface MPMediaItemCollection(Utils)
/** Returns the first media item in the collection
*/

- (MPMediaItem *)firstMediaItem;

/** Returns the last media item in the collection
*/

- (MPMediaItem *)lastMediaItem;

/** This method will return the item in this media collection at a specific index
*/

- (MPMediaItem *)mediaItemAtIndex:(NSUInteger)index;

/** Given a particular media item, this method will return the next media item in the collection.
If there are multiple copies of the same media item in the list, it will return the one
after the first occurrence.
*/

- (MPMediaItem *)mediaItemAfterItem:(MPMediaItem *)compare;

/** Returns the title of the media item at a given index.
*/

- (NSString *)titleForMediaItemAtIndex:(NSUInteger)index;

/** Returns YES if the given media item occurs at least once in this collection
*/

- (BOOL)containsItem:(MPMediaItem *)compare;

/** Creates a new collection by appending otherCollection to the end of this collection
*/

- (MPMediaItemCollection *)collectionByAppendingCollection:(MPMediaItemCollection *)otherCollection;

/** Creates a new collection by appending an array of media items to the end of this collection
*/

- (MPMediaItemCollection *)collectionByAppendingMediaItems:(NSArray *)items;

/** Creates a new collection by appending a single media item to the end of this collection
*/

- (MPMediaItemCollection *)collectionByAppendingMediaItem:(MPMediaItem *)item;

/** Creates a new collection based on this collection, but excluding the specified items.
*/

- (MPMediaItemCollection *)collectionByDeletingMediaItems:(NSArray *)itemsToRemove;

/** Creates a new collection based on this collection, but which doesn't include the specified media item.
*/

- (MPMediaItemCollection *)collectionByDeletingMediaItem:(MPMediaItem *)itemToRemove;

/** Creates a new collection based on this collection, but excluding the media item at the specified index
*/

- (MPMediaItemCollection *)collectionByDeletingMediaItemAtIndex:(NSUInteger)index;

/** Creates a new collection, based on this collection, but excluding the media items starting with
(and including) the objects at index from and ending with (and including) to.
*/

- (MPMediaItemCollection *)collectionByDeletingMediaItemsFromIndex:(NSUInteger)from toIndex:(NSUInteger)to;
@end



MPMediaItemCollection-Utils.m
#import "MPMediaItemCollection-Utils.h"

@implementation MPMediaItemCollection(Utils)
- (MPMediaItem *)firstMediaItem {
return [[self items] objectAtIndex:0];
}


- (MPMediaItem *)lastMediaItem {
return [[self items] lastObject];
}


- (MPMediaItem *)mediaItemAtIndex:(NSUInteger)index {
return [[self items] objectAtIndex:index];
}


- (MPMediaItem *)mediaItemAfterItem:(MPMediaItem *)compare {
NSArray *items = [self items];

for (MPMediaItem *oneItem in items) {
if ([oneItem isEqual:compare]) {
// If last item, there is no index + 1
if (![[items lastObject] isEqual: oneItem])
return [items objectAtIndex:[items indexOfObject:oneItem] + 1];
}

}

return nil;
}


- (NSString *)titleForMediaItemAtIndex:(NSUInteger)index {
MPMediaItem *item = [[self items] objectAtIndex:index];
return [item valueForProperty:MPMediaItemPropertyTitle];
}


- (BOOL)containsItem:(MPMediaItem *)compare {
NSArray *items = [self items];

for (MPMediaItem *oneItem in items) {
if ([oneItem isEqual:compare])
return YES;
}

return NO;
}


- (MPMediaItemCollection *)collectionByAppendingCollection:(MPMediaItemCollection *)otherCollection {
return [self collectionByAppendingMediaItems:[otherCollection items]];
}


- (MPMediaItemCollection *)collectionByAppendingMediaItems:(NSArray *)items {
if (items == nil || [items count] == 0)
return nil;
NSMutableArray *appendCollection = [[[self items] mutableCopy] autorelease];
[appendCollection addObjectsFromArray:items];
return [MPMediaItemCollection collectionWithItems:appendCollection];
}


- (MPMediaItemCollection *)collectionByAppendingMediaItem:(MPMediaItem *)item {
if (item == nil)
return nil;

return [self collectionByAppendingMediaItems:[NSArray arrayWithObject:item]];
}


- (MPMediaItemCollection *)collectionByDeletingMediaItems:(NSArray *)itemsToRemove {
if (itemsToRemove == nil || [itemsToRemove count] == 0)
return [[self copy] autorelease];
NSMutableArray *items = [[[self items] mutableCopy] autorelease];
[items removeObjectsInArray:itemsToRemove];
return [MPMediaItemCollection collectionWithItems:items];
}


- (MPMediaItemCollection *)collectionByDeletingMediaItem:(MPMediaItem *)itemToRemove {
if (itemToRemove == nil)
return [[self copy] autorelease];

NSMutableArray *items = [[[self items] mutableCopy] autorelease];
[items removeObject:itemToRemove];
return [MPMediaItemCollection collectionWithItems:items];
}


- (MPMediaItemCollection *)collectionByDeletingMediaItemAtIndex:(NSUInteger)index {
NSMutableArray *items = [[[self items] mutableCopy] autorelease];
[items removeObjectAtIndex:index];
return [items count] > 0 ? [MPMediaItemCollection collectionWithItems:items] : nil;
}


- (MPMediaItemCollection *)collectionByDeletingMediaItemsFromIndex:(NSUInteger)from toIndex:(NSUInteger)to {

// Ensure from is before to
if (to < from) {
NSUInteger temp = from;
to = from;
from = temp;
}


NSMutableArray *items = [[[self items] mutableCopy] autorelease];
[items removeObjectsInRange:NSMakeRange(from, to - from)];
return [MPMediaItemCollection collectionWithItems:items];
}

@end

 
Design by Wordpress Theme | Bloggerized by Free Blogger Templates | coupon codes