Skip to content

Instantly share code, notes, and snippets.

@aliirz
Created January 18, 2014 14:28
Show Gist options
  • Save aliirz/8491332 to your computer and use it in GitHub Desktop.
Save aliirz/8491332 to your computer and use it in GitHub Desktop.
UIIMage+Tools
//
// UIImage+Tools.h
// FTLibrary
//
// Created by Ondrej Rafaj on 26/09/2011.
// Copyright (c) 2011 Fuerte International. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface UIImage (Tools)
// rotate UIImage to any angle
- (UIImage *)rotate:(UIImageOrientation)orient;
// rotate and scale image from iphone camera
- (UIImage *)rotateAndScaleFromCameraWithMaxSize:(CGFloat)maxSize;
// scale this image to a given maximum width and height
- (UIImage *)scaleWithMaxSize:(CGFloat)maxSize;
- (UIImage *)scaleWithMaxSize:(CGFloat)maxSize quality:(CGInterpolationQuality)quality;
#pragma mark conversion/detection
// converts UIImage to grayscale with 8bpp
+ (UIImage *)convertTo8bppGrayscaleFromImage:(UIImage *)uimage;
// maxSize = -1 when you don't want to scale the image
+ (UIImage *)convertTo8bppGrayscaleFromImage:(UIImage *)image scaleToMaximumSize:(NSInteger) maxSize;
//detects the skew of the UIImage passed, it converts image to grayscale if its not already in grayscale, than calculates the skew in range [degMinimum, degMaximum] with step check equal to degStep
+ (CGFloat)detectSkewOfTheImage:(UIImage *)image withDegreesRangeMinimum:(CGFloat) degMinimum andDegreesMaximum:(CGFloat) degMaximum degreesStep:(CGFloat) degStep;
#pragma mark rotation
// free rotate by given degrees, if degrees > 0 then rotation is ClockWise
+ (UIImage *)rotateImage:(UIImage *)image byDegrees:(CGFloat) degrees;
// rotation to given orientation, useful to change the image rotation after it was made with the camera, this rotates only by 90, -90,
+ (UIImage *)rotateImage:(UIImage *)src andRotateAngle:(UIImageOrientation) orientation;
// if degrees < 0 than rotation is clockWise, otherwise CounterClockWise, degrees are in Deg
+ (CGPoint)rotatePoint:(CGPoint)point byDegrees:(CGFloat) degrees aroundOriginPoint:(CGPoint) origin;
// index 0 gives left-top point of rect, 2 gives right-top, 3 gives left-bottom, 4 gives right-bottom
+ (CGPoint)getPointAtIndex:(NSUInteger)index ofRect:(CGRect)rect;
// returns the size of the space that will use the new rotated image so that it will fit correctly in it
+ (CGSize)imageSizeForRect:(CGRect)rect rotatedByDegreees:(CGFloat)degrees;
@end
//
// UIImage+Tools.m
// FTLibrary
//
// Created by Ondrej Rafaj on 26/09/2011.
// Copyright (c) 2011 Fuerte International. All rights reserved.
//
#import "UIImage+Tools.h"
#import "UIColor+Tools.h"
static inline CGSize swapWidthAndHeight(CGSize size) {
CGFloat swap = size.width;
size.width = size.height;
size.height = swap;
return size;
}
static inline CGFloat degreesToRadians(CGFloat degrees) {
return M_PI * (degrees / 180.0);
}
static int temporaryImageAngle;
static inline CGFloat toRadians (CGFloat degrees) { return degrees * M_PI/180.0f; }
//images on iPhone should be no bigger than 1024, making images bigger than 1024 may cause crashes caused by not enough memory
#define maximumResultImageSize 1024
//indicates how many lines we check, when put 40 in here, 1 line is checked, 40th line, 80th line and so on
//the bigger the number the less concrete the result but faster detection
#define lineCheckingStep 40
@implementation UIImage (Tools)
- (UIImage *)rotate:(UIImageOrientation)orient {
CGRect bnds = CGRectZero;
UIImage *copy = nil;
CGContextRef ctxt = nil;
CGRect rect = CGRectZero;
CGAffineTransform tran = CGAffineTransformIdentity;
bnds.size = self.size;
rect.size = self.size;
switch (orient) {
case UIImageOrientationUp:
return self;
case UIImageOrientationUpMirrored:
tran = CGAffineTransformMakeTranslation(rect.size.width, 0.0);
tran = CGAffineTransformScale(tran, -1.0, 1.0);
break;
case UIImageOrientationDown:
tran = CGAffineTransformMakeTranslation(rect.size.width,
rect.size.height);
tran = CGAffineTransformRotate(tran, degreesToRadians(180.0));
break;
case UIImageOrientationDownMirrored:
tran = CGAffineTransformMakeTranslation(0.0, rect.size.height);
tran = CGAffineTransformScale(tran, 1.0, -1.0);
break;
case UIImageOrientationLeft:
bnds.size = swapWidthAndHeight(bnds.size);
tran = CGAffineTransformMakeTranslation(0.0, rect.size.width);
tran = CGAffineTransformRotate(tran, degreesToRadians(-90.0));
break;
case UIImageOrientationLeftMirrored:
bnds.size = swapWidthAndHeight(bnds.size);
tran = CGAffineTransformMakeTranslation(rect.size.height, rect.size.width);
tran = CGAffineTransformScale(tran, -1.0, 1.0);
tran = CGAffineTransformRotate(tran, degreesToRadians(-90.0));
break;
case UIImageOrientationRight:
bnds.size = swapWidthAndHeight(bnds.size);
tran = CGAffineTransformMakeTranslation(rect.size.height, 0.0);
tran = CGAffineTransformRotate(tran, degreesToRadians(90.0));
break;
case UIImageOrientationRightMirrored:
bnds.size = swapWidthAndHeight(bnds.size);
tran = CGAffineTransformMakeScale(-1.0, 1.0);
tran = CGAffineTransformRotate(tran, degreesToRadians(90.0));
break;
default:
// orientation value supplied is invalid
assert(false);
return nil;
}
UIGraphicsBeginImageContext(bnds.size);
ctxt = UIGraphicsGetCurrentContext();
switch (orient) {
case UIImageOrientationLeft:
case UIImageOrientationLeftMirrored:
case UIImageOrientationRight:
case UIImageOrientationRightMirrored:
CGContextScaleCTM(ctxt, -1.0, 1.0);
CGContextTranslateCTM(ctxt, -rect.size.height, 0.0);
break;
default:
CGContextScaleCTM(ctxt, 1.0, -1.0);
CGContextTranslateCTM(ctxt, 0.0, -rect.size.height);
break;
}
CGContextConcatCTM(ctxt, tran);
CGContextDrawImage(ctxt, rect, self.CGImage);
copy = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return copy;
}
- (UIImage *)rotateAndScaleFromCameraWithMaxSize:(CGFloat)maxSize {
UIImage *imag = self;
imag = [imag rotate:imag.imageOrientation];
imag = [imag scaleWithMaxSize:maxSize];
return imag;
}
- (UIImage *)scaleWithMaxSize:(CGFloat)maxSize {
return [self scaleWithMaxSize:maxSize quality:kCGInterpolationHigh];
}
-(UIImage*)scaleWithMaxSize:(CGFloat)maxSize
quality:(CGInterpolationQuality)quality
{
CGRect bnds = CGRectZero;
UIImage* copy = nil;
CGContextRef ctxt = nil;
CGRect orig = CGRectZero;
CGFloat rtio = 0.0;
CGFloat scal = 1.0;
bnds.size = self.size;
orig.size = self.size;
rtio = orig.size.width / orig.size.height;
if ((orig.size.width <= maxSize) && (orig.size.height <= maxSize))
{
return self;
}
if (rtio > 1.0)
{
bnds.size.width = maxSize;
bnds.size.height = maxSize / rtio;
}
else
{
bnds.size.width = maxSize * rtio;
bnds.size.height = maxSize;
}
UIGraphicsBeginImageContext(bnds.size);
ctxt = UIGraphicsGetCurrentContext();
scal = bnds.size.width / orig.size.width;
CGContextSetInterpolationQuality(ctxt, quality);
CGContextScaleCTM(ctxt, scal, -scal);
CGContextTranslateCTM(ctxt, 0.0, -orig.size.height);
CGContextDrawImage(ctxt, orig, self.CGImage);
copy = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return copy;
}
#pragma mark Image operations
#pragma mark -
#pragma mark Conversion/detection
//converts each UIImage to UIImage with grayscale palette, 8 bits per pixel wide
+ (UIImage *)convertTo8bppGrayscaleFromImage:(UIImage *) uimage {
return [UIImage convertTo8bppGrayscaleFromImage:uimage scaleToMaximumSize:-1];
}
//if maxSize
+ (UIImage *)convertTo8bppGrayscaleFromImage:(UIImage *) uimage scaleToMaximumSize:(NSInteger) maxSize {
int iwidth = uimage.size.width;
int iheight = uimage.size.height;
int maxFromHeightAndWidth = MAX(iwidth, iheight);
float scaleFactor = maxSize / (float)maxFromHeightAndWidth;
if(maxSize == -1){
scaleFactor = 1.0f;
if(maxFromHeightAndWidth > maximumResultImageSize){
scaleFactor = maximumResultImageSize / (float) maxFromHeightAndWidth;
}
}
int newImageWidth = iwidth*scaleFactor;
int newImageHeight = iheight*scaleFactor;
NSAssert(newImageWidth != 0 && newImageHeight != 0, @"Attempt to create 0x0 image");
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
uint8_t *pixels = (uint8_t *) malloc(newImageWidth * newImageHeight * sizeof(pixels));
NSAssert(pixels, @"not enought memory to alloc data for converted image");
CGContextRef context = CGBitmapContextCreate(pixels, newImageWidth, newImageHeight, 8, newImageWidth * sizeof(uint8_t), colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaNone);
CGContextDrawImage(context, CGRectMake(0, 0, newImageWidth, newImageHeight), uimage.CGImage);
CGImageRef image = CGBitmapContextCreateImage(context);
// we're done with the context, color space, and pixels
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
free(pixels);
// make a new UIImage to return
UIImage *resultUIImage = [UIImage imageWithCGImage:image];
// we're done with image now too
CGImageRelease(image);
int bitsPerPixel = CGImageGetBitsPerPixel(resultUIImage.CGImage);
NSAssert(bitsPerPixel == 8, @"Converted image doesn't have 8 bits per pixel size!");
return resultUIImage;
}
/*
Returns an array made with Bresenham's algorithm, each cell of the array represents the following line, value is the number of pixels that should be taken from this line
*/
+ (int *)newNumOfPixelsInEachLineForWidth:(NSInteger) w andAngle:(NSInteger) ang {
int *cntTable = (int *)malloc(sizeof(int)*(ang+1));
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //cause we use many NSNumber objects here - "[NSNumber numberWithInt:]"
NSInteger dLong = w;
NSInteger dShort = ang;
NSInteger err = 3*dShort - 2*dLong;
NSInteger cLong = 0;
NSInteger cShort = 0;
cntTable[cShort] = 1;
while (cLong < dLong) {
if (err >= 0) {
err -= 2*(dLong - dShort);
++cShort;
cntTable[cShort] = 0;
} else {
err += 2*dShort;
}
++cLong;
++cntTable[cShort];
}
[pool release];
return cntTable;
}
//gives number of black pixels in skewed line with angle == [array count], for values given by bres array
+ (NSInteger)getBlackPixelsInLine:(NSInteger) lineNumber forImage:(UIImage *) image withBresArray:(int *) array andTreshold:(unsigned char) blackTreshold negativeAngle:(BOOL)negative {
CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
NSInteger bytesPerLine = CGImageGetBytesPerRow(image.CGImage);
unsigned char *rawData = (unsigned char *)CFDataGetBytePtr(data);
NSInteger blacks = 0;
NSInteger offsetInLine = 0;
unsigned char *ptrToStartLine = (rawData + bytesPerLine*lineNumber);
NSInteger linesSkewNumber = temporaryImageAngle;
for(NSInteger i = 0; i < linesSkewNumber; ++i){ //lines
NSInteger pixelsToTake = array[i];
for(NSInteger j = 0; j < pixelsToTake && offsetInLine < CGImageGetWidth(image.CGImage); ++j, ++offsetInLine){
NSInteger lineOffsetFromStartLine = bytesPerLine*i;
if(negative){
lineOffsetFromStartLine = bytesPerLine * (linesSkewNumber-1-i);
}
NSAssert(lineOffsetFromStartLine >= 0, @"line offset can't be negative!");
unsigned char pixelValue = *(ptrToStartLine + lineOffsetFromStartLine + offsetInLine);
if(pixelValue < blackTreshold){
++blacks;
}
}
}
if(data != NULL){
CFRelease(data);
}
return blacks;
}
//image that is given here has to be 8bpp in greyscale colorSpace
//function counts black pixels for given angle MODULE(it checks for -ang and +ang) and returned value(its module: abs(...)) gives u the number of blacks that have been found(maximum)
//if returned value is positive than, maximum blacks number returned are counted for positive angle otherwise for negative
+ (NSInteger)getBlackPercentageForAngle:(NSInteger) ang forImageData:(UIImage *)image andBlackTreshold:(unsigned char) blackTreshold {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSInteger iwidth = CGImageGetWidth(image.CGImage);
NSInteger iheight = CGImageGetHeight(image.CGImage);
NSInteger blackPixels = 0;
NSInteger blackPixelsForNegative = 0;
NSInteger numberOfLineChecked = 0;
//get array with number of pixels in each line that should be taken to account - Bresenham's algorithm
int *bresArray = [UIImage newNumOfPixelsInEachLineForWidth:iwidth andAngle:ang];
temporaryImageAngle = ang;
NSInteger lineStep = 40;
for(NSInteger i = 0; i < iheight - ang; i += lineStep){
NSInteger blacksInSkeyLine = [UIImage getBlackPixelsInLine:i forImage:image withBresArray:bresArray andTreshold:blackTreshold negativeAngle:NO];
blackPixels += pow(blacksInSkeyLine, 2);
blacksInSkeyLine = [UIImage getBlackPixelsInLine:i forImage:image withBresArray:bresArray andTreshold:blackTreshold negativeAngle:YES];
blackPixelsForNegative += pow(blacksInSkeyLine, 2);
++numberOfLineChecked;
}
free(bresArray);
NSAssert(numberOfLineChecked, @"Division by zero is not allowed!");
NSInteger maximumPixels = MAX(blackPixels, blackPixelsForNegative);
if(blackPixelsForNegative > blackPixels){
maximumPixels = -maximumPixels;
}
[pool release];
return (maximumPixels / numberOfLineChecked);
}
+ (NSInteger)degrees:(CGFloat)degrees inPixelsForImage:(UIImage *) image {
return image.size.width * tanf(toRadians(degrees));
}
+ (CGFloat) detectSkewOfTheImage:(UIImage *) image withDegreesRangeMinimum:(CGFloat) degMinimum andDegreesMaximum:(CGFloat) degMaximum degreesStep:(CGFloat) degStep {
if(CGImageGetBitsPerPixel(image.CGImage) != 8){
image = [UIImage convertTo8bppGrayscaleFromImage:image];
}
unsigned char blackTreshold = 0xFF >> 1; //half of the 256 will be used as the border between black and white
NSInteger detectedAngleInPixels = 0;
NSInteger percentOfBlacksForDetectedAngle = 0;
NSInteger currentCheckingAngleInPixels = [UIImage degrees:degMinimum inPixelsForImage:image];
NSInteger maximumCheckingAngleInPixels = [UIImage degrees:degMaximum inPixelsForImage:image];
NSInteger stepAngle = [UIImage degrees:degStep inPixelsForImage:image];
for(; currentCheckingAngleInPixels < maximumCheckingAngleInPixels && currentCheckingAngleInPixels < image.size.height; currentCheckingAngleInPixels += stepAngle){
NSInteger blackPercentageForGivenAngle = [UIImage getBlackPercentageForAngle:currentCheckingAngleInPixels forImageData:image andBlackTreshold:blackTreshold];
if(abs(blackPercentageForGivenAngle) > percentOfBlacksForDetectedAngle){
percentOfBlacksForDetectedAngle = abs(blackPercentageForGivenAngle);
detectedAngleInPixels = currentCheckingAngleInPixels;
if(blackPercentageForGivenAngle < 0){
detectedAngleInPixels = -detectedAngleInPixels;
}
}
//can be enabled so that we stop searching the skew when we experience decreasing numbers of black pixels
/*if(blackPercentageForGivenAngle < percentOfBlacksForDetectedAngle){
break;
}*/
}
CGFloat imageWidth = CGImageGetWidth(image.CGImage);
CGFloat angle = atanf((CGFloat)detectedAngleInPixels / imageWidth)* 180 /M_PI;
return angle;
}
#pragma mark -
#pragma mark Rotation
+ (unsigned char) avarageColorOfThe8bppImageBorder:(UIImage *) image {
NSAssert(image, @"can't find average color of nil image");
NSInteger bpp = CGImageGetBitsPerPixel(image.CGImage);
if(bpp != 8){
NSLog(@"WARNING: average color of the image border can't be calculated for images having more than 8bpp(yours is %dbpp). Converting to grayscale first.", bpp);
image = [UIImage convertTo8bppGrayscaleFromImage:image];
}
CFDataRef data = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
unsigned char *rawData = (unsigned char *) CFDataGetBytePtr(data);
NSInteger height = image.size.height;
NSInteger width = image.size.width;
NSInteger bytesPerRow = CGImageGetBytesPerRow(image.CGImage);
NSInteger avarageColor = 0;
NSInteger samplesTakenInHorizontal = width >> 1;
NSInteger samplesTakenInVertical = height >> 1;
NSInteger w, h; //random
h = 0;
unsigned char c;
for(NSInteger i = 0; i < samplesTakenInHorizontal; i+=2){
//top line border
h = 0;
w = arc4random() % width;
c = *(rawData + bytesPerRow*h + w);
avarageColor += c;
//bottom line
h = height - 1;
w = arc4random() % width;
c = *(rawData + bytesPerRow*h + w);
avarageColor += c;
}
for(NSInteger i = 0; i < samplesTakenInVertical; i += 2){
//left line
w = 0;
h = arc4random() % height;
c = *(rawData + bytesPerRow*h + w);
avarageColor += c;
//right line
w = width - 1;
h = arc4random() % height;
c = *(rawData + bytesPerRow*h + w);
avarageColor += c;
}
avarageColor = avarageColor / (samplesTakenInVertical+samplesTakenInHorizontal);
if(data != NULL){
CFRelease(data);
}
return avarageColor;
}
//if degrees < 0 than rotation is clockWise, otherwise CounterClockWise
+ (CGPoint) rotatePoint:(CGPoint)point byDegrees:(CGFloat) degrees aroundOriginPoint:(CGPoint) origin {
CGPoint rotated = CGPointMake(0.0f, 0.0f);
CGFloat radians = toRadians(degrees);
rotated.x = cos(radians) * (point.x-origin.x) - sin(radians) * (point.y-origin.y) + origin.x;
rotated.y = sin(radians) * (point.x-origin.x) + cos(radians) * (point.y-origin.y) + origin.y;
return rotated;
}
+ (CGPoint) getPointAtIndex:(NSUInteger) index ofRect:(CGRect) rect {
NSAssert1(index >= 0 && index < 4, @"Rectangle has 4 corners, index should be between [0,3], u passed %d", index);
CGPoint point = rect.origin;
if(index == 1){
point.x += CGRectGetWidth(rect);
} else if(index == 2){
point.y += CGRectGetHeight(rect);
} else if(index == 3){
point.y += CGRectGetHeight(rect);
point.x += CGRectGetWidth(rect);
}
return point;
}
+ (CGSize) imageSizeForRect:(CGRect) rect rotatedByDegreees:(CGFloat) degrees {
CGPoint rotationOrigin = CGPointMake(0.0f, 0.0f);
CGFloat maxX = 0, minX = INT_MAX, maxY = 0, minY = INT_MAX;
for(NSInteger i = 0; i < 4; ++i){
CGPoint toRotate = [UIImage getPointAtIndex:i ofRect:rect];
CGPoint rotated = [UIImage rotatePoint:toRotate byDegrees:degrees aroundOriginPoint:rotationOrigin];
minX = MIN(minX, rotated.x);
minY = MIN(minY, rotated.y);
maxX = MAX(maxX, rotated.x);
maxY = MAX(maxY, rotated.y);
}
CGSize newSize = CGSizeMake(maxX - minX, maxY - minY);
return newSize;
}
//clockwise when degrees < 0
+ (UIImage *) rotateImage:(UIImage *) image byDegrees:(CGFloat) degrees {
CGSize newImageSize = [UIImage imageSizeForRect:CGRectMake(0.0f, 0.0f, image.size.width, image.size.height) rotatedByDegreees:degrees];
//if the new ImageSize will be bigger than 1024 then we need to scale the image
CGFloat maximum = MAX(newImageSize.width, newImageSize.height);
CGFloat scaleFactor = 1.0f;
if(maximum > maximumResultImageSize){
scaleFactor = maximumResultImageSize/maximum;
}
UIGraphicsBeginImageContext(CGSizeMake(newImageSize.width*scaleFactor, newImageSize.height*scaleFactor));
CGContextRef context = UIGraphicsGetCurrentContext();
UIGraphicsPushContext(context);
CGRect drawingRect = CGRectMake(0.0f, 0.0f, newImageSize.width*scaleFactor, newImageSize.height*scaleFactor);
unsigned char midColor = [UIImage avarageColorOfThe8bppImageBorder:image];
[[UIColor colorWithRed:midColor/255.0 green:midColor/255.0 blue:midColor/255.0 alpha:1.0f] set];
CGContextFillRect(context, CGRectInset(drawingRect, -2, -2));
CGContextTranslateCTM(context, drawingRect.size.width/2, drawingRect.size.height/2);
CGContextRotateCTM(context, toRadians(degrees));
[image drawInRect:CGRectMake((-image.size.width*scaleFactor)/2, (-image.size.height*scaleFactor)/2, image.size.width*scaleFactor, image.size.height*scaleFactor)];
UIGraphicsPopContext();
UIImage *copy = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return copy;
}
+ (UIImage *)rotateImage:(UIImage *)src andRotateAngle:(UIImageOrientation)orientation {
UIGraphicsBeginImageContext(src.size);
CGContextRef context = UIGraphicsGetCurrentContext();
UIGraphicsPushContext(context);
if (orientation == UIImageOrientationRight) {
CGContextRotateCTM (context, toRadians(90));
}
else if (orientation == UIImageOrientationLeft) {
CGContextRotateCTM (context, toRadians(-90));
}
else if (orientation == UIImageOrientationDown) {
// NOTHING
}
else if (orientation == UIImageOrientationUp) {
CGContextTranslateCTM(context, src.size.width, 0.0f);
CGContextRotateCTM (context, toRadians(90));
}
[src drawAtPoint:CGPointMake(0, 0)];
UIGraphicsPopContext();
return UIGraphicsGetImageFromCurrentImageContext();
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment