Can CGContextClipToMask mask all non-transparent pixels with alpha=1? - ios

Is it possible to make CGContextClipToMask ignore the grayscale values of the mask image and work as if it was plain black and white?
I have a grayscale image, and when I use it as a mask gray color are interpreted as an alpha channel. This is fine except for a point where I need to completely mask those pixels that are not transparent.
Short example:
UIImage *mask = [self prepareMaskImage];
UIGraphicsBeginImageContextWithOptions(mask.size, NO, mask.scale); {
// Custom code
CGContextClipToMask(UIGraphicsGetCurrentContext(), mask.size, mask.CGImage);
// Custom code
}
Is it possible to adapt this code to achieve my goal?
Long story short: I need to make a transparent grayscale image become transparent where it originally was and completely black where it's solid-colored.

Interesting problem! Here's code that does what I think you want in a simple sample project. Similar to above but handles scale properly. Also has option to retain the alpha in the mask image if you want. Quick hacked together test that seems to work.

My rough idea would be the following:
You convert your input image to readable byte data in grey + alpha format. Maybe you have to do RGBA instead due to iOS limitations.
You iterate over the byte data modifying the values in place.
To simplify access, use a
typedef struct RGBA {
UInt8 red;
UInt8 green;
UInt8 blue;
UInt8 alpha;
} RGBA;
Let's assume image your input mask.
// First step, using RGBA (because I know it works and does not harm, just writes/consumes twice the amount of memory)
CGImageRef imageRef = image.CGImage;
NSInteger rawWidth = CGImageGetWidth(imageRef);
NSInteger rawHeight = CGImageGetHeight(imageRef);
NSInteger rawBitsPerComponent = 8;
NSInteger rawBytesPerPixel = 4;
NSInteger rawBytesPerRow = rawBytesPerPixel * rawWidth;
CGRect rawRect = CGRectMake(0, 0, rawWidth, rawHeight);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
UInt8 *rawImage = (UInt8 *)malloc(rawHeight * rawWidth * rawBytesPerPixel);
CGContextRef rawContext = CGBitmapContextCreate(rawImage,
rawWidth,
rawHeight,
rawBitsPerComponent,
rawBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
// At this point, rawContext is ready for drawing, everything drawn will be in rawImage's byte array.
CGContextDrawImage(rawContext, rawRect, imageRef);
// Second step, crawl the byte array and do the evil work:
for (NSInteger y = 0; y < rawHeight; ++y) {
for (NSInteger x = 0; x < rawWidth; ++x) {
UInt8 *address = rawImage + x * rawBytesPerPixel + y * rawBytesPerRow;
RGBA *pixel = (RGBA *)address;
// If it is a grey input image, it does not matter what RGB channel to use - they shall all be the same
if (0 != pixel->red) {
pixel->alpha = 0;
} else {
pixel->alpha = UINT8_MAX;
}
pixel->red = 0;
pixel->green = 0;
pixel->blue = 0;
// I am still not sure if this is the transformation you are searching for, but it may give you the idea.
}
}
// Third: rawContext is ready, transformation is done. Get the image out of it
CGImageRef outputImage1 = CGBitmapContextCreateImage(rawContext);
UIImage *outputImage2 = [UIImage imageWithCGImage:outputImage1];
CGImageRelease(outputImage1);
Okay... the output is RGBA, but you can create a greyscale + alpha context and just blit your image there for conversion.

This piece of code helped me applying hue on non-transparent area of an image.
- (UIImage*)imageWithImage:(UIImageView*)source colorValue:(CGFloat)hue {
CGSize imageSize = [source.image size];
CGRect imageExtent = CGRectMake(0,0,imageSize.width,imageSize.height);
// Create a context containing the image.
UIGraphicsBeginImageContext(imageSize);
CGContextRef context = UIGraphicsGetCurrentContext();
[source.image drawAtPoint:CGPointMake(0, 0)];
// Setup a clip region using the image
CGContextSaveGState(context);
CGContextClipToMask(context, source.bounds, source.image.CGImage);
self.imageColor = [UIColor colorWithHue:hue saturation:1.0 brightness:1 alpha:1.0];
[self.imageColor set];
CGContextFillRect(context, source.bounds);
// Draw the hue on top of the image.
CGContextSetBlendMode(context, kCGBlendModeHue);
[self.imageColor set];
UIBezierPath *imagePath = [UIBezierPath bezierPathWithRect:imageExtent];
[imagePath fill];
CGContextRestoreGState(context); // remove clip region
// Retrieve the new image.
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}

Related

Xcode remove selected color from an image? [duplicate]

I want to change the color of an opaque UIImage. My original image is as follows:
and I want to convert the image to the following format
So, basically I want to convert red color in the image into black (Any other color) color. Above two images are added for better understanding.
I couldn't see any answers on the 'duplicates' (this question shouldn't have been flagged as a duplicate) that will let you replace a given color with another color and work on an opaque image, so I decided to add one that would.
I created a UIImage category to do this, it basically works by looping through each pixel and detecting how close it is to a given colour, and blends it with your replacement colour if it is.
This will work for images with both transparency and opaque backgrounds.
#implementation UIImage (UIImageColorReplacement)
-(UIImage*) imageByReplacingColor:(UIColor*)sourceColor withMinTolerance:(CGFloat)minTolerance withMaxTolerance:(CGFloat)maxTolerance withColor:(UIColor*)destinationColor {
// components of the source color
const CGFloat* sourceComponents = CGColorGetComponents(sourceColor.CGColor);
UInt8* source255Components = malloc(sizeof(UInt8)*4);
for (int i = 0; i < 4; i++) source255Components[i] = (UInt8)round(sourceComponents[i]*255.0);
// components of the destination color
const CGFloat* destinationComponents = CGColorGetComponents(destinationColor.CGColor);
UInt8* destination255Components = malloc(sizeof(UInt8)*4);
for (int i = 0; i < 4; i++) destination255Components[i] = (UInt8)round(destinationComponents[i]*255.0);
// raw image reference
CGImageRef rawImage = self.CGImage;
// image attributes
size_t width = CGImageGetWidth(rawImage);
size_t height = CGImageGetHeight(rawImage);
CGRect rect = {CGPointZero, {width, height}};
// bitmap format
size_t bitsPerComponent = 8;
size_t bytesPerRow = width*4;
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big;
// data pointer
UInt8* data = calloc(bytesPerRow, height);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// create bitmap context
CGContextRef ctx = CGBitmapContextCreate(data, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
CGContextDrawImage(ctx, rect, rawImage);
// loop through each pixel's components
for (int byte = 0; byte < bytesPerRow*height; byte += 4) {
UInt8 r = data[byte];
UInt8 g = data[byte+1];
UInt8 b = data[byte+2];
// delta components
UInt8 dr = abs(r-source255Components[0]);
UInt8 dg = abs(g-source255Components[1]);
UInt8 db = abs(b-source255Components[2]);
// ratio of 'how far away' each component is from the source color
CGFloat ratio = (dr+dg+db)/(255.0*3.0);
if (ratio > maxTolerance) ratio = 1; // if ratio is too far away, set it to max.
if (ratio < minTolerance) ratio = 0; // if ratio isn't far enough away, set it to min.
// blend color components
data[byte] = (UInt8)round(ratio*r)+(UInt8)round((1.0-ratio)*destination255Components[0]);
data[byte+1] = (UInt8)round(ratio*g)+(UInt8)round((1.0-ratio)*destination255Components[1]);
data[byte+2] = (UInt8)round(ratio*b)+(UInt8)round((1.0-ratio)*destination255Components[2]);
}
// get image from context
CGImageRef img = CGBitmapContextCreateImage(ctx);
// clean up
CGContextRelease(ctx);
CGColorSpaceRelease(colorSpace);
free(data);
free(source255Components);
free(destination255Components);
UIImage* returnImage = [UIImage imageWithCGImage:img];
CGImageRelease(img);
return returnImage;
}
#end
Usage:
UIImage* colaImage = [UIImage imageNamed:#"cola1.png"];
UIImage* blackColaImage = [colaImage imageByReplacingColor:[UIColor colorWithRed:1 green:0 blue:0 alpha:1] withMinTolerance:0.5 withMaxTolerance:0.6 withColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1]];
The minTolerance is the point at which pixels will start to blend with the replacement colour (rather than being replaced). The maxTolerance is the point at which the pixels will stop being blended.
Before:
After:
The result is a little aliased, but bear in mind that your original image was fairly small. This will work much better with a higher resolution image. You can also play about with the tolerances to get even better results!
You can use this
theImageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[theImageView setTintColor:[UIColor blackColor]];
I think something wrong with your image here is my image, it is in white .
here is the change in simulator
Note: First, You need to set your image's Background as transparent with Photoshop or any other tool.
Then use below code.
Easy way,
You need to use rendering mode to change this color.
cokeImageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; // yourimageviewname
[cokeImageView setTintColor:[UIColor blackColor]];

Core Graphics result image not working when edited again

I've got two methods I'm working with, and they aren't playing nicely. The first is a Perlin noise generator, which exports a black-and-white UIImage of random clouds, and it's working perfectly. The second method takes a UIImage and filters out all pixels above or below a given brightness, returning an image with transparency where the unwanted pixels were, and it's working perfectly with the black-and-white test images I've been using.
But when I try to feed an image from the first method into the second, it doesn't work. Every pixel gets removed, regardless of the input values, and I get back a blank UIImage. (To be clear, that's a non-nil UIImage with nothing but transparent pixels, as though every pixel is being counted as outside the desired brightness range, regardless of that pixel's actual brightness.)
Below are the two methods. I adapted each from tutorials and SO answers, but while I'm not 100% comfortable with Core Graphics, they seem reasonably simple to me: the first iterates through each pixel and colors it with RGB values from a Perlin formula, and the second creates a mask based on input values. (Note: both are category methods on UIImage, so the "self" references in the latter method are referring to the source image.)
+ (UIImage *)perlinMapOfSize:(CGSize)size {
CZGPerlinGenerator *generator = [[CZGPerlinGenerator alloc] init];
generator.octaves = 10;
generator.persistence = 0.5;
generator.zoom = 150;
CGContextRef ctx = [self contextSetup:size];
CGContextSetRGBFillColor(ctx, 0.000, 0.000, 0.000, 1.000);
CGContextFillRect(ctx, CGRectMake(0.0, 0.0, size.width, size.height));
for (CGFloat x = 0.0; x<size.width; x+=1.0) {
for (CGFloat y=0.0; y<size.height; y+=1.0) {
double value = [generator perlinNoiseX:x y:y z:0 t:0];
CGContextSetRGBFillColor(ctx, value, value, value, 1.0);
CGContextFillRect(ctx, CGRectMake(x, y, 1.0, 1.0));
}
}
return [self finishImageContext];
}
-(UIImage*)imageWithLumaMaskFromDark:(CGFloat)lumaFloor toLight:(CGFloat)lumaCeil {
// inputs range from 0 - 255
CGImageRef rawImageRef = self.CGImage;
const CGFloat colorMasking[6] = {lumaFloor, lumaCeil, lumaFloor, lumaCeil, lumaFloor, lumaCeil};
UIGraphicsBeginImageContext(self.size);
CGImageRef maskedImageRef = CGImageCreateWithMaskingColors(rawImageRef, colorMasking);
{
//if in iphone
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 0.0, self.size.height);
CGContextScaleCTM(UIGraphicsGetCurrentContext(), 1.0, -1.0);
}
CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, self.size.width, self.size.height), maskedImageRef);
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
CGImageRelease(maskedImageRef);
UIGraphicsEndImageContext();
return result;
}
Does anyone know why an image from the former method would be incompatible with the latter? The former method is successfully returning cloud images, and the latter method is working with every image I feed into it from my computer or the internet, just not the images from the former method.
I'm assuming that the CGImageCreateWithMaskingColors() call in the second method is looking for some information that the first method isn't putting into the image, or something, I just don't know the system well enough to figure out what's wrong.
Can anyone shed some light?
EDIT: As requested, here are the two other methods referenced above. It's an odd setup, I know, to use class methods like that in a category, but it's how I found the code in a tutorial and it works so I never bothered to change it.
+ (CGContextRef) contextSetup: (CGSize) size {
UIGraphicsBeginImageContext(size);
CGContextRef context = UIGraphicsGetCurrentContext();
UIGraphicsPushContext(context);
//NSLog(#"Begin drawing");
return context;
}
+ (UIImage *) finishImageContext {
//NSLog(#"End drawing");
UIGraphicsPopContext();
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
EDIT 2: Based on some research that the CGImageCreateWithMaskingColors() function doesn't work with images that include alpha components, I've rearranged the first method like so. My gut tells me this was the problem, but I'm kind of casting about in the dark. This is my attempt at trying to create an image with kCGImageAlphaNone, but now UIGraphicsGetImageFromCurrentImageContext() at the end is returning nil.
+ (UIImage *)perlinMapOfSize:(CGSize)size {
CZGPerlinGenerator *generator = [[CZGPerlinGenerator alloc] init];
generator.octaves = 10;
generator.persistence = 0.5;
generator.zoom = 150;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef ctx = CGBitmapContextCreate(NULL, size.width, size.height, 8, size.width * 4, colorSpace, kCGImageAlphaNoneSkipLast);
UIGraphicsPushContext(ctx);
CGContextSetRGBFillColor(ctx, 0.000, 0.000, 0.000, 1.0);
CGContextFillRect(ctx, CGRectMake(0.0, 0.0, size.width, size.height));
for (int x = 0; x<size.width; x++) {
for (int y=0; y<size.height; y++) {
double value = [generator perlinNoiseX:x y:y z:0 t:0];
CGContextSetRGBFillColor(ctx, value, value, value, 1.0);
CGContextFillRect(ctx, CGRectMake(x, y, 1.0, 1.0));
}
}
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
NSLog(#"Output: %#", outputImage);
UIGraphicsEndImageContext();
CGContextRelease(ctx);
return outputImage;
}
As you've discovered, CGImageCreateWithMaskingColors requires an image without an alpha channel. However your attempted fix doesn't work as #matt points out because you're trying to mix and match image context function calls (e.g UIGraphicsGetImageFromCurrentImageContext) with a bitmap context.
The simplest fix therefore is to simply carry on using an image context, but set it to be opaque. You can do this by calling UIGraphicsBeginImageContextWithOptions and supplying YES into the opaque argument, which will output an image without an alpha channel.
Although that being said, using a bitmap context here would be a more appropriate solution as it allows you to manipulate the pixel data directly, rather than doing a ton of CGContextFillRect calls.
Something like this should achieve the desired result:
+(UIImage *)perlinMapOfSize:(CGSize)size {
// your generator setup
CZGPerlinGenerator *generator = [[CZGPerlinGenerator alloc] init];
generator.octaves = 10;
generator.persistence = 0.5;
generator.zoom = 150;
// bitmap info
size_t width = size.width;
size_t height = size.height;
size_t bytesPerPixel = 4;
size_t bitsPerComponent = 8;
size_t bytesPerRow = width * bytesPerPixel;
CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big;
// allocate memory for the bitmap
UInt8* imgData = calloc(bytesPerRow, height);
// create RGB color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// create an RGBA bitmap context where the alpha component is ignored
CGContextRef ctx = CGBitmapContextCreate(imgData, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
// iterate over pixels
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
// the current pixel index
size_t byte = x * bytesPerPixel + y * bytesPerRow;
// get noise data for given x & y
int value = round([generator perlinNoiseX:x y:y z:0 t:0]*255.0);
// limit values (not too sure of the range of values that the method outputs – this may not be needed)
if (value > 255) value = 255;
else if (value < 0) value = 0;
// write values to pixel components
imgData[byte] = value; // R
imgData[byte+1] = value; // G
imgData[byte+2] = value; // B
}
}
// get image
CGImageRef imgRef = CGBitmapContextCreateImage(ctx);
UIImage* img = [UIImage imageWithCGImage:imgRef];
// clean up
CGContextRelease(ctx);
CGColorSpaceRelease(colorSpace);
CGImageRelease(imgRef);
free(imgData);
return img;
}
A few other things to note
UIGraphicsBeginImageContext(WithOptions) automatically makes the image context the current context – thus you don't need to do UIGraphicsPushContext/UIGraphicsPopContext with it.
UIGraphicsBeginImageContext uses a scale of 1.0 – meaning you're working with sizes in pixels, not points. Therefore the images you ouput may not be suitable for 2x or 3x displays. You should usually be using UIGraphicsBeginImageContextWithOptions instead, with a scale of 0.0 (the main screen scale) or image.scale if you're just manipulating a given image (appropriate for your imageWithLumaMaskFromDark method).
CGBitmapContextCreate will also create a context with a scale of 1.0. If you want to scale the image to the same scale as your screen, you'll want to simply multiply the width and height that you input by the screen scale:
CGFloat scale = [UIScreen mainScreen].scale;
size_t width = size.width*scale;
size_t height = size.height*scale;
and then supply the scale when you create the output UIImage:
UIImage* img = [UIImage imageWithCGImage:imgRef scale:scale orientation:UIImageOrientationUp];
If you want to do some CGContext drawing calls in the bitmap context, you'll also want to scale it before drawing, so you can work in a points coordinate system:
CGContextScaleCTM(ctx, scale, scale);

How to replace a given color with another on an opaque UIImage

I want to change the color of an opaque UIImage. My original image is as follows:
and I want to convert the image to the following format
So, basically I want to convert red color in the image into black (Any other color) color. Above two images are added for better understanding.
I couldn't see any answers on the 'duplicates' (this question shouldn't have been flagged as a duplicate) that will let you replace a given color with another color and work on an opaque image, so I decided to add one that would.
I created a UIImage category to do this, it basically works by looping through each pixel and detecting how close it is to a given colour, and blends it with your replacement colour if it is.
This will work for images with both transparency and opaque backgrounds.
#implementation UIImage (UIImageColorReplacement)
-(UIImage*) imageByReplacingColor:(UIColor*)sourceColor withMinTolerance:(CGFloat)minTolerance withMaxTolerance:(CGFloat)maxTolerance withColor:(UIColor*)destinationColor {
// components of the source color
const CGFloat* sourceComponents = CGColorGetComponents(sourceColor.CGColor);
UInt8* source255Components = malloc(sizeof(UInt8)*4);
for (int i = 0; i < 4; i++) source255Components[i] = (UInt8)round(sourceComponents[i]*255.0);
// components of the destination color
const CGFloat* destinationComponents = CGColorGetComponents(destinationColor.CGColor);
UInt8* destination255Components = malloc(sizeof(UInt8)*4);
for (int i = 0; i < 4; i++) destination255Components[i] = (UInt8)round(destinationComponents[i]*255.0);
// raw image reference
CGImageRef rawImage = self.CGImage;
// image attributes
size_t width = CGImageGetWidth(rawImage);
size_t height = CGImageGetHeight(rawImage);
CGRect rect = {CGPointZero, {width, height}};
// bitmap format
size_t bitsPerComponent = 8;
size_t bytesPerRow = width*4;
CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big;
// data pointer
UInt8* data = calloc(bytesPerRow, height);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// create bitmap context
CGContextRef ctx = CGBitmapContextCreate(data, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
CGContextDrawImage(ctx, rect, rawImage);
// loop through each pixel's components
for (int byte = 0; byte < bytesPerRow*height; byte += 4) {
UInt8 r = data[byte];
UInt8 g = data[byte+1];
UInt8 b = data[byte+2];
// delta components
UInt8 dr = abs(r-source255Components[0]);
UInt8 dg = abs(g-source255Components[1]);
UInt8 db = abs(b-source255Components[2]);
// ratio of 'how far away' each component is from the source color
CGFloat ratio = (dr+dg+db)/(255.0*3.0);
if (ratio > maxTolerance) ratio = 1; // if ratio is too far away, set it to max.
if (ratio < minTolerance) ratio = 0; // if ratio isn't far enough away, set it to min.
// blend color components
data[byte] = (UInt8)round(ratio*r)+(UInt8)round((1.0-ratio)*destination255Components[0]);
data[byte+1] = (UInt8)round(ratio*g)+(UInt8)round((1.0-ratio)*destination255Components[1]);
data[byte+2] = (UInt8)round(ratio*b)+(UInt8)round((1.0-ratio)*destination255Components[2]);
}
// get image from context
CGImageRef img = CGBitmapContextCreateImage(ctx);
// clean up
CGContextRelease(ctx);
CGColorSpaceRelease(colorSpace);
free(data);
free(source255Components);
free(destination255Components);
UIImage* returnImage = [UIImage imageWithCGImage:img];
CGImageRelease(img);
return returnImage;
}
#end
Usage:
UIImage* colaImage = [UIImage imageNamed:#"cola1.png"];
UIImage* blackColaImage = [colaImage imageByReplacingColor:[UIColor colorWithRed:1 green:0 blue:0 alpha:1] withMinTolerance:0.5 withMaxTolerance:0.6 withColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:1]];
The minTolerance is the point at which pixels will start to blend with the replacement colour (rather than being replaced). The maxTolerance is the point at which the pixels will stop being blended.
Before:
After:
The result is a little aliased, but bear in mind that your original image was fairly small. This will work much better with a higher resolution image. You can also play about with the tolerances to get even better results!
You can use this
theImageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[theImageView setTintColor:[UIColor blackColor]];
I think something wrong with your image here is my image, it is in white .
here is the change in simulator
Note: First, You need to set your image's Background as transparent with Photoshop or any other tool.
Then use below code.
Easy way,
You need to use rendering mode to change this color.
cokeImageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; // yourimageviewname
[cokeImageView setTintColor:[UIColor blackColor]];

Core Graphics invert alpha

Currently I'm having problems with CCClippingNode in Cocos2D and transitions as stated here. Now Viktor (author of SpriteBuilder) gave a tip that I could mask the image with Core Graphics.
Which I now am doing, and it works. However Core Graphics uses the alpha the exact opposite for masking as Cocos2d. Is it possible to invert the alpha value of an UIImage (grayscale) on the fly?
If I want to mask a circle out of an image with Cocos I would need this mask.
but it needs to be this in core graphics.
Is it possible to create the bottom image from the top image on the fly? (e.g inverting it's alpha)
How to convert UIImage/CGImageRef's alpha channel to mask?
Ben's answer to the linked question solves inverting alpha of given UIImage. Just delete/comment the below section of the code
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
unsigned char val = alphaData[y*strideLength + x];
val = 255 - val;
alphaData[y*strideLength + x] = val;
}
PS: couldn't add comment.
I get the answer based on this answer honors for #Tommy
CGSize size = finalImage.size;
int width = size.width;
int height = size.height;
// Create a suitable RGB+alpha bitmap context in BGRA colour space
CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *memoryPool = (unsigned char *)calloc(width*height*4, 1);
CGContextRef context = CGBitmapContextCreate(memoryPool, width, height, 8, width * 4, colourSpace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colourSpace);
// draw the current image to the newly created context
CGContextDrawImage(context, CGRectMake(0, 0, width, height), [finalImage CGImage]);
// run through every pixel, a scan line at a time...
for(int y = 0; y < height; y++)
{
// get a pointer to the start of this scan line
unsigned char *linePointer = &memoryPool[y * width * 4];
// step through the pixels one by one...
for(int x = 0; x < width; x++)
{
if(linePointer[3]){
linePointer[3]=255-linePointer[3];
}
linePointer += 4;
}
}
// get a CG image from the context, wrap that into a
// UIImage
CGImageRef cgImage = CGBitmapContextCreateImage(context);
UIImage *returnImage = [UIImage imageWithCGImage:cgImage];
// clean up
CGImageRelease(cgImage);
CGContextRelease(context);
free(memoryPool);
//use returnImage as you want

OCR: Image to text?

Before mark as copy or repeat question, please read the whole question first.
I am able to do at pressent is as below:
To get image and crop the desired part for OCR.
Process the image using tesseract and leptonica.
When the applied document is cropped in chunks ie 1 character per image it provides 96% of accuracy.
If I don't do that and the document background is in white color and text is in black color it gives almost same accuracy.
For example if the input is as this photo :
Photo start
Photo end
What I want is to able to get the same accuracy for this photo
without generating blocks.
The code I used to init tesseract and extract text from image is as below:
For init of tesseract
in .h file
tesseract::TessBaseAPI *tesseract;
uint32_t *pixels;
in .m file
tesseract = new tesseract::TessBaseAPI();
tesseract->Init([dataPath cStringUsingEncoding:NSUTF8StringEncoding], "eng");
tesseract->SetPageSegMode(tesseract::PSM_SINGLE_LINE);
tesseract->SetVariable("tessedit_char_whitelist", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
tesseract->SetVariable("language_model_penalty_non_freq_dict_word", "1");
tesseract->SetVariable("language_model_penalty_non_dict_word ", "1");
tesseract->SetVariable("tessedit_flip_0O", "1");
tesseract->SetVariable("tessedit_single_match", "0");
tesseract->SetVariable("textord_noise_normratio", "5");
tesseract->SetVariable("matcher_avg_noise_size", "22");
tesseract->SetVariable("image_default_resolution", "450");
tesseract->SetVariable("editor_image_text_color", "40");
tesseract->SetVariable("textord_projection_scale", "0.25");
tesseract->SetVariable("tessedit_minimal_rejection", "1");
tesseract->SetVariable("tessedit_zero_kelvin_rejection", "1");
For get text from image
- (void)processOcrAt:(UIImage *)image
{
[self setTesseractImage:image];
tesseract->Recognize(NULL);
char* utf8Text = tesseract->GetUTF8Text();
int conf = tesseract->MeanTextConf();
NSArray *arr = [[NSArray alloc]initWithObjects:[NSString stringWithUTF8String:utf8Text],[NSString stringWithFormat:#"%d%#",conf,#"%"], nil];
[self performSelectorOnMainThread:#selector(ocrProcessingFinished:)
withObject:arr
waitUntilDone:YES];
free(utf8Text);
}
- (void)ocrProcessingFinished0:(NSArray *)result
{
UIAlertView *alt = [[UIAlertView alloc]initWithTitle:#"Data" message:[result objectAtIndex:0] delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alt show];
}
But I don't get proper output for the number plate image either it is null or it gives some garbage data for the image.
And if I use the image which is the first one ie white background with text as black then the output is 89 to 95% accurate.
Please help me out.
Any suggestion will be appreciated.
Update
Thanks to #jcesar for providing the link and also to #konstantin pribluda to provide valuable information and guide.
I am able to convert images in to proper black and white form (almost). and so the recognition is better for all images :)
Need help with proper binarization of images. Any Idea will be appreciated
Hi all Thanks for your replies, from all of that replies I am able to get this conclusion as below:
I need to get the only one cropped image block with number plate contained in it.
From that plate need to find out the portion of the number portion using the data I got using the method provided here.
Then converting the image data to almost black and white using the RGB data found through the above method.
Then the data is converted to the Image using the method provided here.
Above 4 steps are combined in to one method like this as below :
-(void)getRGBAsFromImage:(UIImage*)image
{
NSInteger count = (image.size.width * image.size.height);
// 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 = (unsigned char*) calloc(height * width * 4, sizeof(unsigned char));
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 = 0;
for (int ii = 0 ; ii < count ; ++ii)
{
CGFloat red = (rawData[byteIndex] * 1.0) ;
CGFloat green = (rawData[byteIndex + 1] * 1.0) ;
CGFloat blue = (rawData[byteIndex + 2] * 1.0) ;
CGFloat alpha = (rawData[byteIndex + 3] * 1.0) ;
NSLog(#"red %f \t green %f \t blue %f \t alpha %f rawData [%d] %d",red,green,blue,alpha,ii,rawData[ii]);
if(red > Required_Value_of_red || green > Required_Value_of_green || blue > Required_Value_of_blue)//all values are between 0 to 255
{
red = 255.0;
green = 255.0;
blue = 255.0;
alpha = 255.0;
// all value set to 255 to get white background.
}
rawData[byteIndex] = red;
rawData[byteIndex + 1] = green;
rawData[byteIndex + 2] = blue;
rawData[byteIndex + 3] = alpha;
byteIndex += 4;
}
colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bitmapContext = CGBitmapContextCreate(
rawData,
width,
height,
8, // bitsPerComponent
4*width, // bytesPerRow
colorSpace,
kCGImageAlphaNoneSkipLast);
CFRelease(colorSpace);
CGImageRef cgImage = CGBitmapContextCreateImage(bitmapContext);
UIImage *img = [UIImage imageWithCGImage:cgImage];
//use the img for further use of ocr
free(rawData);
}
Note:
The only drawback of this method is the time consumed and the RGB value to convert to white and other to black.
UPDATE :
CGImageRef imageRef = [plate CGImage];
CIContext *context = [CIContext contextWithOptions:nil]; // 1
CIImage *ciImage = [CIImage imageWithCGImage:imageRef]; // 2
CIFilter *filter = [CIFilter filterWithName:#"CIColorMonochrome" keysAndValues:#"inputImage", ciImage, #"inputColor", [CIColor colorWithRed:1.f green:1.f blue:1.f alpha:1.0f], #"inputIntensity", [NSNumber numberWithFloat:1.f], nil]; // 3
CIImage *ciResult = [filter valueForKey:kCIOutputImageKey]; // 4
CGImageRef cgImage = [context createCGImage:ciResult fromRect:[ciResult extent]];
UIImage *img = [UIImage imageWithCGImage:cgImage];
Just replace the above method's(getRGBAsFromImage:) code with this one and the result is same but the time taken is just 0.1 to 0.3 second only.
I was able to achieve near instant results using the demo photo provided as well as it generating the correct letters.
I pre-processed the image using GPUImage
// Pre-processing for OCR
GPUImageLuminanceThresholdFilter * adaptiveThreshold = [[GPUImageLuminanceThresholdFilter alloc] init];
[adaptiveThreshold setThreshold:0.3f];
[self setProcessedImage:[adaptiveThreshold imageByFilteringImage:_image]];
And then sending that processed image to TESS
- (NSArray *)processOcrAt:(UIImage *)image {
[self setTesseractImage:image];
_tesseract->Recognize(NULL);
char* utf8Text = _tesseract->GetUTF8Text();
return [self ocrProcessingFinished:[NSString stringWithUTF8String:utf8Text]];
}
- (NSArray *)ocrProcessingFinished:(NSString *)result {
// Strip extra characters, whitespace/newlines
NSString * results_noNewLine = [result stringByReplacingOccurrencesOfString:#"\n" withString:#""];
NSArray * results_noWhitespace = [results_noNewLine componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSString * results_final = [results_noWhitespace componentsJoinedByString:#""];
results_final = [results_final lowercaseString];
// Separate out individual letters
NSMutableArray * letters = [[NSMutableArray alloc] initWithCapacity:results_final.length];
for (int i = 0; i < [results_final length]; i++) {
NSString * newTile = [results_final substringWithRange:NSMakeRange(i, 1)];
[letters addObject:newTile];
}
return [NSArray arrayWithArray:letters];
}
- (void)setTesseractImage:(UIImage *)image {
free(_pixels);
CGSize size = [image size];
int width = size.width;
int height = size.height;
if (width <= 0 || height <= 0)
return;
// the pixels will be painted to this array
_pixels = (uint32_t *) malloc(width * height * sizeof(uint32_t));
// clear the pixels so any transparency is preserved
memset(_pixels, 0, width * height * sizeof(uint32_t));
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// create a context with RGBA pixels
CGContextRef context = CGBitmapContextCreate(_pixels, width, height, 8, width * sizeof(uint32_t), colorSpace,
kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedLast);
// paint the bitmap to our context which will fill in the pixels array
CGContextDrawImage(context, CGRectMake(0, 0, width, height), [image CGImage]);
_tesseract->SetImage((const unsigned char *) _pixels, width, height, sizeof(uint32_t), width * sizeof(uint32_t));
}
This left ' marks for the - but these are also easy to remove. Depending on the image set that you have you may have to fine tune it a bit but it should get you moving in the right direction.
Let me know if you have problems using it, it's from a project I'm using and I didn't want to have to strip everything out or create a project from scratch for it.
I daresay that tesseract will be overkill for your purpose. You do not need dictionary matching to improve recognition quality ( you do not have this dictionary , but maybe means to compute checksum on license number ), and you have font optimised for OCR.
And best of all, you have markers (orange and blue color areas nearby are good) to find region in the image.
I my OCR apps I use human assisted area of interest retrieval ( just aiming help overlay over camera preview). Usually ones uses something like haar cascade to locate interesting features like faces. You may also calculate centroid of orange area, or just bounding box of orange pixels simply by traversing all the image and stoing leftmost / rightmost / topmost / bottommost pixels of suitable color
As for recognition itselff I would recommend to use invariant moments ( not sure whether implemented in tesseract, but you can easily port it from out java project: http://sourceforge.net/projects/javaocr/ )
I tried my demo app on monitor image and it recognized digits on the sport (is not trained
for characters)
As for binarisation ( separating black from white ) I would recommend sauvola method as this gives best tolerance to luminance changes ( also implemented in our OCR project )

Resources