cloud goes social

Icon

computer, travel, movies, music, cuisine and more

ImageMagick on iPhone – Xcode

UPDATE: iOS4.3 and Xcode4

PLEASE READ THE UPDATE

UPDATE! (30.08.09)
Thanks to Jon Kean (see comments) the downloadable project now shows how to use images with unusual number of bits per component. As usual look at the defines at the top to check the functionality you want.

UPDATE! (14.07.09)
Thanks to Karl (see comments) the downloadable project now shows different ways of integrating ImageMagick with Objective-C UIImages offering both compressed methods, using JPEG compression as an example, and raw-data methods. Look at the define at the top to change which method the program will be using.

Many of you have asked me if I could post an Xcode project example to use the libraries and ImageMagick, and so, here it is.

The code is self-describing so I didn’t comment it much, it’s just a few functions call. All it does in this case is loading an image from an UIImage (Objective-C) into a format that ImageMagick (C) understands and can manipulate. Then it applies an ImageMagick filter (OrderedPosterize), which uses a configuration file (thresholds.xml).

Everything is working fine, and you have the complete set of libraries, headers and configuration files, and a configured XCode project right at hand.

All downloadable from here

If you still have any problem, don’t hesitate to contact me!

ciop ciop

Category: Code, tips & howtos

Tagged: , ,

15 Responses

  1. Mike says:

    hi,

    Been playing around with this against OpenGL fragment shader and have a quick question i would be grateful for your assistance with as im still getting to grips with imagemagick

    how can i execute the following using this library?

    kk=w*0.5;
    ll=h*0.5;
    dx=(i-kk);
    dy=(j-ll);
    aa=atan2(dy,dx);
    rr=hypot(dy,dx);
    rs=sqrt(rr*200);
    px=kk+rs*cos(aa);
    py=ll+rs*sin(aa);
    p{px,py}

    Thanks,
    Mike

  2. samar says:

    it’s really nice tutorial but i want to run imagemagick command through the code on the image how can it be possible……
    any idea???

  3. Jon Kean says:

    the code for pulling in UIImages directly (without compression) doesn’t work for images with an unusual number of bits per component (such as 5-bit components).

    Since ImageMagick doesn’t support components with arbitrary bit depth (only 8/16/32/etc), the simplest solution is to convert the UIImage into a consistent format before processing with ImageMagick.

    Here is something to do just that (also fixed a leaking data provider):

    CGImageRef createStandardImage(CGImageRef image) {
    	const size_t width = CGImageGetWidth(image);
    	const size_t height = CGImageGetHeight(image);
    	CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
    	CGContextRef ctx = CGBitmapContextCreate(NULL, width, height, 8, 4*width, space,
    											 kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedFirst);
    	CGColorSpaceRelease(space);
    	CGContextDrawImage(ctx, CGRectMake(0, 0, width, height), image);
    	CGImageRef dstImage = CGBitmapContextCreateImage(ctx);
    	CGContextRelease(ctx);
    	return dstImage;
    }
    
    - (UIImage*) createPosterizeImage:(CGImageRef)srcCGImage {
    	const unsigned long width = CGImageGetWidth(srcCGImage);
    	const unsigned long height = CGImageGetHeight(srcCGImage);
    
    	// could use the image directly if it has 8/16 bits per component,
    	// otherwise the image must be converted into something more common (such as images with 5-bits per component)
    	// here we'll be simple and always convert 
    	const char *map = "ARGB"; // hard coded
    	const StorageType inputStorage = CharPixel;
    	CGImageRef standardized = createStandardImage(srcCGImage);
    	NSData *srcData = (NSData *) CGDataProviderCopyData(CGImageGetDataProvider(standardized));
    	CGImageRelease(standardized);
    	
    	const void *bytes = [srcData bytes];
    	MagickWandGenesis();
    	MagickWand *magick_wand = NewMagickWand();
    	
    	MagickBooleanType status = MagickConstituteImage(magick_wand, width, height, map, inputStorage, bytes);
    	if (status == MagickFalse) {
    		ThrowWandException(magick_wand);
    	}
    	
    	status = MagickOrderedPosterizeImage(magick_wand, "h8x8o");
    	if (status == MagickFalse) {
    		ThrowWandException(magick_wand);
    	}
    	
    	const int bitmapBytesPerRow = (width * strlen(map));
    	const int bitmapByteCount = (bitmapBytesPerRow * height);
    	CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    	char *trgt_image = malloc(bitmapByteCount);
    	status = MagickExportImagePixels(magick_wand, 0, 0, width, height, map, CharPixel, trgt_image);
    	if (status == MagickFalse) {
    		ThrowWandException(magick_wand);
    	}
    	
    	magick_wand = DestroyMagickWand(magick_wand);
    	MagickWandTerminus();
    	
    	CGContextRef context = CGBitmapContextCreate (trgt_image,
    												  width,
    												  height,
    												  8, // bits per component
    												  bitmapBytesPerRow,
    												  colorSpace,
    												  kCGImageAlphaPremultipliedFirst);
    	CGColorSpaceRelease(colorSpace);
    	
    	CGImageRef cgimage = CGBitmapContextCreateImage(context);
    	UIImage *image = [[UIImage alloc] initWithCGImage:cgimage];
    	CGImageRelease(cgimage);
    	CGContextRelease(context);
    	[srcData release];
    	free(trgt_image);
    	
    	return image;
    }
    
  4. sachin says:

    hi cloud

    thanks a lot for guidance
    how can i store UIImage into custom location and process the file using ImageMagick.

  5. Karl says:

    Brilliant. It’s also short on any releasing probably :-)

  6. Cloud says:

    @Karl: amazing job. I will put it into the XCode project for everyone to look at it in a ‘better formatted’ way. I will also look into some benchmarking! :)

  7. Karl says:

    Here we go, the final piece of the puzzle… getting the pixels back out and into a UIImage. It appears that this way we avoid any compression, so it must be quicker and non-lossy.

    This code assumes that it follows the code above, and basically tries to reconstruct an image in the same format as the original. Apologies for the desperately messy code… it’s the thought that counts ;-)

    CGColorSpaceRef colorSpace;
    int bitmapByteCount;
    int bitmapBytesPerRow;
    CGContextRef    context = NULL;
    	
    bitmapBytesPerRow = (width * 4);
    bitmapByteCount = (bitmapBytesPerRow * height);
    colorSpace = CGColorSpaceCreateDeviceRGB();
    	
    char *my_image = malloc(bitmapBytesPerRow * height);
    status = MagickExportImagePixels(magick_wand,0,0,width,height,map,CharPixel,my_image);
    if (status == MagickFalse) {
    	ThrowWandException(magick_wand);
    }
    	
    context = CGBitmapContextCreate (my_image,
    								 width,
    								 height,
    									 8,      // bits per component
    								 bitmapBytesPerRow,
    								 colorSpace,
    								 bitmapInfo);
    	
    CGColorSpaceRelease( colorSpace );
    	
    CGImageRef cgimage = CGBitmapContextCreateImage(context);
    UIImage *image = [[UIImage alloc] initWithCGImage:cgimage];
    
  8. Karl says:

    Ok, I’ve worked out how to get data into MagickWand without going through a compressed format. The “map” endianness is a hack to make it work with my current image and environments – more work required here!

    NSData *srcData = (NSData*) CGDataProviderCopyData(CGImageGetDataProvider([src CGImage]));
    	unsigned long width = src.size.width;
    	unsigned long height = src.size.height;
    	const void *bytes = [srcData bytes];
    	const char *map;
    	CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(srcImage);
    	if ((bitmapInfo & kCGBitmapByteOrder32Little) != 0)
    		map = "BGRA"; // DEVICE
    	else
    		map = "ARGB"; // SIMULATOR
    	status = MagickConstituteImage(magick_wand, width, height, map, CharPixel, bytes);
    	if (status == MagickFalse) {
    		ThrowWandException(magick_wand);
    	}
    

    And now you have the image in the wand. To get it back out I am currently setting the format to JPG and getting it out as a JPG blob as before. Without setting the format it appears to be a raw image format that ImageMagick uses internally? Not much use.

    status = MagickSetFormat(magick_wand, "jpg");
    size_t my_size;
    unsigned char * my_image = MagickGetImageBlob(magick_wand, &my_size);
    

    Haven’t performance tested yet. Hope it’s an improvement – at least it’s one less compression.

  9. Karl says:

    Thank you very much for your efforts getting ImageMagick working on the iPhone, and for posting it here.

    Do you think there is a way to get image data from a UIImage / CGImage into and out of ImageMagick without going through a JPEG or PNG encode? It’s an unfortunate and time consuming extra step in the process!

    • Cloud says:

      @Karl: I don’t know, but remember that ImageMagick expects the image to be a .gif, a .jpeg or a .png encoded image. Therefore I think that step is necessary. If anyone has a better solution I’ll gladly try it and see the results!

Leave a Reply

Donate

If you appreciate my work, my applications, this blog in general or you simply feel like rewarding me for something please consider donate.
Any amount will motivate me in keeping up with the blog and the applications.


Thank you!

Donors

F. Olsen, W. Chang, W. Edmondson, C. Sharff, M. Brown, G. Helton, J. de Ruiter, K. Langner, K. Smith, T. OHalloran, B. Zabarauskas

Listening to ...