clipping a UIImage as per a polygon - iphone

How can I let the user of my iphone app to clip a UIImage by a dynamically generated CGPath. Basically I display a rectangle overlaid on a UIImageView and the user can move the 4 corners of the rectangle to get a polygon with 4 sides. The rectangle is not filled so you see four lines overlaid on an image.
The user should be able to clip out whatever is outside the 4 lines.
Any help or pointers is much appreciated.

If you already have the CGPath, you just have to use CGContextAddPath and CGContextClip, and after that you can draw your UIImage on that context.
If you just want to display the clipped image, that context could be the current context in the DrawRect method of your view.
If you actually want to have the clipped image data, the context would probably be a CGBitmapContext, something like this:
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
size_t bytesPerPixel = 1;
size_t bytesPerRow = bmpWidth * bytesPerPixel;
size_t bmpDataSize = ( bytesPerRow * bmpHeight);
unsigned char *bmpData = malloc(bmpDataSize);
memset(bmpData, 0, bmpDataSize);
CGContextRef bmpCtx = CGBitmapContextCreate(bmpData, bmpWidth, bmpHeight, 8, bytesPerRow, colorSpace, kCGImageAlphaNone | kCGBitmapByteOrderDefault);
(the code example is for a grey scale bitmap because I had that code ready, but it's not hard to figure out what has to be changed for an RGB bitmap.)
then to actually draw the clipped image to the bitmap context you would do something like this (I'm writing this code from memory, so there might be some mistakes):
// theContext could be
// UIGraphicsGetCurrentContext()
// or the bmpCtx
CGContextAddPath(theContext, yourCGPath);
CGContextClip(theContext);
// not sure you need the translate and scale...
CGContextTranslateCTM(theContext, 0, bmpHeight);
CGContextScaleCTM(theContext, 1, -1);
CGContextDrawImage(theContext, rect, yourUIImage.CGImage);

Related

How to layer on the CGContext

I'm using a bit of a workaround in order to successfully save an image with a mask (because you can't render masks with CoreAction).
Here's my code:
CGContextRef context = CGBitmapContextCreate(nil, cWidth, cHeight, bitsPerComponent, bytesPerRow, CGColorSpaceCreateDeviceRGB(), CGImageGetBitmapInfo(image));
//the mask:
CGContextClipToMask(context, CGRectMake(0,0,1280,935), self.image.image.CGImage);
//the image to mask:
CGContextDrawImage(context, CGRectMake(0, 0, 1280,935), viewImage.CGImage);
CGImageRef mergeResult = CGBitmapContextCreateImage(context);
saver = [[UIImage alloc] initWithCGImage:mergeResult];
So this works pretty well, the mask cuts out everything outside of its shape in the target image. This makes the surrounding area and background white. Rather than show white, I would like to show an image/color/pattern, etc. So I'd basically like to stack another image behind all that.
How can this be done? Thanks
Draw your background into the context first, then clip, then draw your image into the same context.

Apple GLPaint modification problem

I followed Apple's GLPaint paint application and tried to modify it.. In sample code they have used simple particle.png to draw.
My question is i want to use some other image of my choice for drawing. At first sight it seems very easy to replace "particle.png" with some "finger.png" but it did not work.. When I replaced "particle.png" with "finger.png" , I got something like this :
My "finger.png" image looks somthing like this :
Link : http://developer.apple.com/library/ios/#samplecode/GLPaint/Listings/Classes_PaintingView_m.html#//apple_ref/doc/uid/DTS40007328-Classes_PaintingView_m-DontLinkElementID_6
Partial Code:
- (id)initWithCoder:(NSCoder*)coder
{
CGSize myShadowOffset = CGSizeMake (0, 0);
NSMutableArray* recordedPaths;
CGImageRef brushImage;
CGContextRef brushContext;
GLubyte *brushData;
size_t width, height;
// Create a texture from an image
// First create a UIImage object from the data in a image file, and then extract the Core Graphics image
////--------------------Modification--------------------------------///////
brushImage = [UIImage imageNamed:#"finger.png"].CGImage;
////--------------------Modification--------------------------------///////
// Get the width and height of the image
width = CGImageGetWidth(brushImage);
height = CGImageGetHeight(brushImage);
NSLog(#"%f%f",(CGFloat)width, (CGFloat)height);
// Texture dimensions must be a power of 2. If you write an application that allows users to supply an image,
// you'll want to add code that checks the dimensions and takes appropriate action if they are not a power of 2.
// Make sure the image exists
if(brushImage)
{
// Allocate memory needed for the bitmap context
brushData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
// Use the bitmatp creation function provided by the Core Graphics framework.
brushContext = CGBitmapContextCreate(brushData, width, height, 8, width * 4, CGImageGetColorSpace(brushImage), kCGImageAlphaPremultipliedLast);
// After you create the context, you can draw the image to the context.
CGContextDrawImage(brushContext, CGRectMake(0.0, 0.0, (CGFloat)width, (CGFloat)height), brushImage);
// You don't need the context at this point, so you need to release it to avoid memory leaks.
CGContextRelease(brushContext);
// Use OpenGL ES to generate a name for the texture.
glGenTextures(1, &brushTexture);
// Bind the texture name.
glBindTexture(GL_TEXTURE_2D, brushTexture);
// Set the texture parameters to use a minifying filter and a linear filer (weighted average)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Specify a 2D texture image, providing the a pointer to the image data in memory
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, brushData);
// Release the image data; it's no longer needed
free(brushData);
}
I do not understand why I am getting drawing like this. Can anyone point me out that what other changes I need to make to make this application work as before?? I am not expert at OpenGL so any help or suggestion will be appreciated..
If I remember correctly, you have to make the image white on transparent in order for it to work. If you have blue with transparency around it, it will show the entire picture as opaque.
I took the standard Apple GLPaint app. I replaced particle.png with a finger.png that I made in Photoshop. It is 64x64 RGB 8 bits. The entire image is transparent except for a white smudge which I copied directly from your blue finger.png. Here is the output in the simulator:
It's a bit late, but I find if you change #define kBrushScale in PaintingView.h, you get interesting effects. Try changing to .25, .5. .75 1.0 etc...

Create a mask from difference between two images (iPhone)

How can I detect the difference between 2 images, creating a mask of the area that's different in order to process the area that's common to both images (gaussian blur for example)?
EDIT: I'm currently using this code to get the RGBA value of pixels:
+ (NSArray*)getRGBAsFromImage:(UIImage*)image atX:(int)xx andY:(int)yy count:(int)count
{
NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
// First get the image into your data buffer
CGImageRef imageRef = [image CGImage];
NSUInteger width = CGImageGetWidth(imageRef);
NSUInteger height = CGImageGetHeight(imageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = malloc(height * width * 4);
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * width;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, width, height,
bitsPerComponent, bytesPerRow, colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
CGContextRelease(context);
// Now your rawData contains the image data in the RGBA8888 pixel format.
int byteIndex = (bytesPerRow * yy) + xx * bytesPerPixel;
for (int ii = 0 ; ii < count ; ++ii)
{
CGFloat red = (rawData[byteIndex] * 1.0) / 255.0;
CGFloat green = (rawData[byteIndex + 1] * 1.0) / 255.0;
CGFloat blue = (rawData[byteIndex + 2] * 1.0) / 255.0;
CGFloat alpha = (rawData[byteIndex + 3] * 1.0) / 255.0;
byteIndex += 4;
UIColor *acolor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
[result addObject:acolor];
}
free(rawData);
return result;
}
The problem is, the images are being captured from the iPhone's camera so they are not exactly the same position. I need to create areas of a couple of pixels and extracting the general color of the area (maybe by adding up the RGBA values and dividing by the number of pixels?). How could I do this and then translate it to a CGMask?
I know this is a complex question, so any help is appreciated.
Thanks.
I think the simplest way to do this would be to use a difference blend mode. The following code is based on code I use in CKImageAdditions.
+ (UIImage *) differenceOfImage:(UIImage *)top withImage:(UIImage *)bottom {
CGImageRef topRef = [top CGImage];
CGImageRef bottomRef = [bottom CGImage];
// Dimensions
CGRect bottomFrame = CGRectMake(0, 0, CGImageGetWidth(bottomRef), CGImageGetHeight(bottomRef));
CGRect topFrame = CGRectMake(0, 0, CGImageGetWidth(topRef), CGImageGetHeight(topRef));
CGRect renderFrame = CGRectIntegral(CGRectUnion(bottomFrame, topFrame));
// Create context
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if(colorSpace == NULL) {
printf("Error allocating color space.\n");
return NULL;
}
CGContextRef context = CGBitmapContextCreate(NULL,
renderFrame.size.width,
renderFrame.size.height,
8,
renderFrame.size.width * 4,
colorSpace,
kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
if(context == NULL) {
printf("Context not created!\n");
return NULL;
}
// Draw images
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextDrawImage(context, CGRectOffset(bottomFrame, -renderFrame.origin.x, -renderFrame.origin.y), bottomRef);
CGContextSetBlendMode(context, kCGBlendModeDifference);
CGContextDrawImage(context, CGRectOffset(topFrame, -renderFrame.origin.x, -renderFrame.origin.y), topRef);
// Create image from context
CGImageRef imageRef = CGBitmapContextCreateImage(context);
UIImage * image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGContextRelease(context);
return image;
}
There are three reasons pixels will change from one iPhone photo to the next, the subject changed, the iPhone moved, and random noise. I assume for this question, you're most interested in the subject changes, and you want to process out the effects of the other two changes. I also assume the app intends the user to keep the iPhone reasonably still, so iPhone movement changes are less significant than subject changes.
To reduce the effects of random noise, just blur the image a little. A simple averaging blur, where each pixel in the resulting image is an average of the original pixel with its nearest neighbors should be sufficient to smooth out any noise in a reasonably well lit iPhone image.
To address iPhone movement, you can run a feature detection algorithm on each image (look up feature detection on Wikipedia for a start). Then calculate the transforms needed to align the least changed detected features.
Apply that transform to the blurred images, and find the difference between the images. Any pixels with a sufficient difference will become your mask. You can then process the mask to eliminate any islands of changed pixels. For example, a subject may be wearing a solid colored shirt. The subject may move from one image to the next, but the area of the solid colored shirt may overlap resulting in a mask with a hole in the middle.
In other words, this is a significant and difficult image processing problem. You won't find the answer in a stackoverflow.com post. You will find the answer in a digital image processing textbook.
Can't you just subtract pixel values from the images, and process pixels where the difference i 0?
Every pixel which does not have a suitably similar pixel in the other image within a certain radius can be deemed to be part of the mask. It's slow, (though there's not much that would be faster) but it works fairly simply.
Go through the pixels, copy the ones that are different in the lower image to a new one (not opaque).
Blur the upper one completely, then show the new one above.

mask text inside uitextview/uiwebview

finally I choose to devote some time to find a way/implementation to
mask text inside UITextView/UIWebView.
By now what I'm able to do is:
- add some custom background
- add a uitextview/uiwebview with some text
- add an UIImageView (with a covering png) or a CAGradientLayer to
create a simple mask effect (*)
Of course this is not a magic bullet and require at least one more
layer (the one pointed out with *).
Furthermore it's not so good when you have a full transparent
background 'cause everyone can recognize the extra view/layer used to
fade away the text.
I searched all over google but still not found a good solution (I've
found about mask an image, blah blah)...
Any tips?
Thanks in advance,
marcio
PS maybe a screenshot will be more straightforward, here you're!
http://grab.by/KzS
Yes! I finally got it. I don't know if it's the Apple's way but it works. Maybe they have the opportunity to employ some private apis. Anyway this is a sort of pseudo-algorithm on how I got it works:
1) get a screenshot of the window
2) crop the desired rect with CGImageCreateWithImageInRect
3) apply a gradient mask (stolen from Apple' sample code on Reflections)
4) create an UIImageView with the freshly created image
I also noted that it doesn't affect the performances even on the lowest devices.
Hope it will be helpful!
And this is a crop of the result (link text)
I've promised to myself to implement a category just to make it better. Until now the code is quite spread in different classes.
Just to make a sample (supported only landscape orientation, see the transform below, supported only top mask). In this case I overrided didMoveToWindow of the table that needs to be masked:
- (void)didMoveToWindow {
if (self.window) {
UIImageView *reflected = (UIImageView *)[self.superview viewWithTag:TABLE_SHADOW_TOP];
if (!reflected) {
UIImage *image = [UIImage screenshot:self.window];
//
CGRect croppedRect = CGRectMake(480-self.frame.size.height, self.frame.origin.x, 16, self.frame.size.width);
CGImageRef cropImage = CGImageCreateWithImageInRect(image.CGImage, croppedRect);
UIImage *reflectedImage = [UIImage imageMaskedWithGradient:cropImage];
CGImageRelease(cropImage);
UIImageView *reflected = [[UIImageView alloc] initWithImage:reflectedImage];
reflected.transform = CGAffineTransformMakeRotation(-(M_PI/2));
reflected.tag = TABLE_SHADOW_TOP;
CGRect adjusted = reflected.frame;
adjusted.origin = self.frame.origin;
reflected.frame = adjusted;
[self.superview addSubview:reflected];
[reflected release];
}
}
}
and this is the uiimage category:
CGImageRef CreateGradientImage(int pixelsWide, int pixelsHigh)
{
CGImageRef theCGImage = NULL;
// gradient is always black-white and the mask must be in the gray colorspace
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
// create the bitmap context
CGContextRef gradientBitmapContext = CGBitmapContextCreate(NULL, pixelsWide, pixelsHigh,
8, 0, colorSpace, kCGImageAlphaNone);
// define the start and end grayscale values (with the alpha, even though
// our bitmap context doesn't support alpha the gradient requires it)
CGFloat colors[] = {0.0, 1.0, 1.0, 1.0};
// create the CGGradient and then release the gray color space
CGGradientRef grayScaleGradient = CGGradientCreateWithColorComponents(colorSpace, colors, NULL, 2);
CGColorSpaceRelease(colorSpace);
// create the start and end points for the gradient vector (straight down)
CGPoint gradientStartPoint = CGPointZero;
// CGPoint gradientStartPoint = CGPointMake(0, pixelsHigh);
CGPoint gradientEndPoint = CGPointMake(pixelsWide/1.75, 0);
// draw the gradient into the gray bitmap context
CGContextDrawLinearGradient(gradientBitmapContext, grayScaleGradient, gradientStartPoint,
gradientEndPoint, kCGGradientDrawsAfterEndLocation);
CGGradientRelease(grayScaleGradient);
// convert the context into a CGImageRef and release the context
theCGImage = CGBitmapContextCreateImage(gradientBitmapContext);
CGContextRelease(gradientBitmapContext);
// return the imageref containing the gradient
return theCGImage;
}
CGContextRef MyCreateBitmapContext(int pixelsWide, int pixelsHigh)
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// create the bitmap context
CGContextRef bitmapContext = CGBitmapContextCreate (NULL, pixelsWide, pixelsHigh, 8,
0, colorSpace,
// this will give us an optimal BGRA format for the device:
(kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst));
CGColorSpaceRelease(colorSpace);
return bitmapContext;
}
+ (UIImage *)imageMaskedWithGradient:(CGImageRef)image {
UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
DEBUG(#"need to support deviceOrientation: %i", deviceOrientation);
float width = CGImageGetWidth(image);
float height = CGImageGetHeight(image);
// create a bitmap graphics context the size of the image
CGContextRef mainViewContentContext = MyCreateBitmapContext(width, height);
// create a 2 bit CGImage containing a gradient that will be used for masking the
// main view content to create the 'fade' of the reflection. The CGImageCreateWithMask
// function will stretch the bitmap image as required, so we can create a 1 pixel wide gradient
CGImageRef gradientMaskImage = CreateGradientImage(width, 1);
// create an image by masking the bitmap of the mainView content with the gradient view
// then release the pre-masked content bitmap and the gradient bitmap
CGContextClipToMask(mainViewContentContext, CGRectMake(0.0, 0.0, width, height), gradientMaskImage);
CGImageRelease(gradientMaskImage);
// draw the image into the bitmap context
CGContextDrawImage(mainViewContentContext, CGRectMake(0, 0, width, height), image);
// create CGImageRef of the main view bitmap content, and then release that bitmap context
CGImageRef reflectionImage = CGBitmapContextCreateImage(mainViewContentContext);
CGContextRelease(mainViewContentContext);
// convert the finished reflection image to a UIImage
UIImage *theImage = [UIImage imageWithCGImage:reflectionImage];
// image is retained by the property setting above, so we can release the original
CGImageRelease(reflectionImage);
return theImage;
}
Hope it helps.

Capturing EAGLview content WITH alpha channel on iPhone

have been struggling with this issue for quite some time now and couldn't find an answer so far. Basically, what I want to do, is capturing the content of my EAGLview and then use it to merge it with other images. Anyway, the mainproblem is, that everything transparent in my EAGLview renders opaque when saving it to the photoalbum or putting it into a UIImageView. Let me share some code with you, I found somewhere else:
- (CGImageRef) glToUIImage {
unsigned char buffer[320*480*4];
glReadPixels(0,0,320,480,GL_RGBA,GL_UNSIGNED_BYTE,&buffer);
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, &buffer, 320*480*4, NULL);
CGImageRef iref = CGImageCreate(320,480,8,32,320*4,CGColorSpaceCreateDeviceRGB(),kCGBitmapByteOrderDefault,ref,NULL,true,kCGRenderingIntentDefault);
size_t width = CGImageGetWidth(iref);
size_t height = CGImageGetHeight(iref);
size_t length = width*height*4;
uint32_t *pixels = (uint32_t *)malloc(length);
CGContextRef context = CGBitmapContextCreate(pixels, width, height, 8, width*4, CGImageGetColorSpace(iref), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextTranslateCTM(context, 0.0, height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, CGRectMake(0.0, 0.0, width, height), iref);
CGImageRef outputRef = CGBitmapContextCreateImage(context);
UIImage *outputImage = [UIImage imageWithCGImage:outputRef];
UIImageWriteToSavedPhotosAlbum(outputImage, nil, nil, nil);
return outputRef;
}
As I already mentioned, this perfectly grabs the content of my EAGLview, but I can not get the image with its alpha values.
Any help appreciated. Thanks!
Two places I can see that you might be losing your transparency:
when you're drawing your scene: does your scene have a transparent background? make sure you're doing a glClear to something like (0,0,0,0) rather than (0,0,0,1).
when you're drawing the image to flip it over: what is the default background color here? Seems likely it's a non-transparent black and you'll end up with that where the transparent parts of your scene used to be.
You could check if #2 is your problem by saving the image before you flip it over, and if it is, you could avoid the flipping over process by flipping the memory in your pixels buffer directly rather than using Core Graphics calls.