Image rotation cutting edge on drawrect - objective-c

I am rotating a view and masking the shape of the view using CAShapeLayer. I have view frame and bezier path of drawing shape, it is cutting the edges of my shape when i draw it using CGContext to make its sized shape. Here is my code:
- (UIImage*) crop:(UIImage*)image rect:(CGRect)rect withRadian:(CGFloat)radian
{
float degree = RADIANS_TO_DEGREES(radian);
// // translated rectangle for drawing sub image
// float degree1 = fabsf(degree);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// translated rectangle for drawing sub image
CGRect drawRect = CGRectMake(-rect.origin.x, -rect.origin.y, image.size.width, image.size.height);
// grab image
UIImage* croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return croppedImage;
}
Can anyone please tell me what the problem is?

Related

CGImageCreateWithImageInRect [duplicate]

This question already has answers here:
Losing image orientation while converting an image to CGImage
(7 answers)
Closed 4 years ago.
I am trying to create a simple cropping app. I have a view overlayed on a UIImage and I want to crop a new image out of only the part of the UIImage where the view is overlayed. I used the code
CGRect cropRect = _cropView.frame;
UIImage *cropImage = [UIImage imageWithData:imageData];
CGImageRef cropped = CGImageCreateWithImageInRect([cropImage CGImage], cropRect);
UIImage *croppedImage = [UIImage imageWithCGImage:cropped];
self.imageView.image = croppedImage;
but it resulted in an unexpected image that seemed very zoomed in and not what I wanted. How can I crop a new image from the image behind the view?
A CGImage deals with pixels, not "points" (the abstract measurement unit of UIKit). When the crop view's contentScaleFactor is larger than 1, its pixel dimensions will be larger than its point dimensions. So, to get a crop rect that corresponds to the pixels, you need to multiply all of the coordinates and dimensions of the view's frame by its contentScaleFactor.
CGRect cropRect = _cropView.frame;
cropRect.origin.x *= _cropView.contentScaleFactor;
cropRect.origin.y *= _cropView.contentScaleFactor;
cropRect.size.width *= _cropView.contentScaleFactor;
cropRect.size.height *= _cropView.contentScaleFactor;
May be this help you,
// Create new image context
CGSize size = _cropView.frame.size;
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
// Create rect for image
CGRect rect = _cropView.frame;
// Draw the image into the rect
[existingImage drawInRect:rect];
// Saving the image, ending image context
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

Saving 2 UIImages to one while saving rotation, resize info and its quality

I want to save 2 UIImages that are moved, resized and rotated by user. The problem is i dont want to use such function as any 'printscreen one', because it makes both images to lose a lot from their quality (resolution).
Atm i use something like this:
UIGraphicsBeginImageContext(image1.size);
[image1 drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
[image2 drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
However ofc it just adds two images, their rotation, resizing and moving isn't operated here. Can anybody help with considering these 3 aspects in coding? Any help is appreciated!
My biggest thanks in advance :)
EDIT: images can be rotated and zoomed by user (handling touch events)!
You have to set the transform of the context to match your imageView's transform before you start drawing into it.
i.e.,
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, boundingRect.size.width/2, boundingRect.size.height/2);
transform = CGAffineTransformRotate(transform, angle);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
CGContextConcatCTM(context, transform);
// Draw the image into the context
CGContextDrawImage(context, CGRectMake(-imageView.image.size.width/2, -imageView.image.size.height/2, imageView.image.size.width, imageView.image.size.height), imageView.image.CGImage);
// Get an image from the context
rotatedImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];
and check out Creating a UIImage from a rotated UIImageView.
EDIT: if you don't know the angle of rotation of the image you can get the transform from the layer property of the UIImageView:
UIGraphicsBeginImageContext(rotatedImageView.image.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform transform = rotatedImageView.transform;
transform = CGAffineTransformScale(transform, 1.0, -1.0);
CGContextConcatCTM(context, transform);
// Draw the image into the context
CGContextDrawImage(context, CGRectMake(0, 0, rotatedImageView.image.size.width, rotatedImageView.image.size.height), rotatedImageView.image.CGImage);
// Get an image from the context
UIImage *newRotatedImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];
UIGraphicsEndImageContext();
You will have to play about with the transform matrix to centre the image in the context and you will also have to calculate a bounding rectangle for the rotated image or it will be cropped at the corners (i.e., rotatedImageView.image.size is not big enough to encompass a rotated version of itself).
Try this:
UIImage *temp = [[UIImage alloc] initWithCGImage:image1 scale:1.0 orientation: yourOrientation];
[temp drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
Similarly for image2. Rotation and resizing are handled by orientation and scale respectively. yourOrientation is a UIImageOrientation enum variable and can have a value from 0-7(check this apple documentation on different UIImageOrientation values). Hope it helps...
EDIT: To handle rotations, just write the desired orientation for the rotation you require. You can rotate 90 deg left/right or flip vertically/horizontally. For eg, in the apple documentation, UIImageOrientationUp is 0, UIImageOrientationDown is 1 and so on. Check out my github repo for an example.

Creating a gradient fill for text using [UIColor colorWithPatternImage:]

I want to create a gradient for the fill color of my text. Currently I am doing it by setting the color of a UILabel's text as
UIImage *image = [UIImage imageNamed:#"GradientFillImage.png"];
myLabel.textColor = [UIColor colorWithPatternImage:image];
Where GradientFillImage.png is a simple image file with a linear gradient painted on it.
This works fine until I want to resize the font. Since the image file is of constant dimensions and does not resize when I resize the font, the gradient fill for the font gets messed up.
How do I create a custom size pattern image and apply it as a fill pattern for text?
I've just finished a UIColor class extension that makes this a 1 line + block thing.
https://github.com/bigkm/UIColor-BlockPattern
CGRect rect = CGRectMake(0.0,0.0,10.0,10.0);
[UIColor colorPatternWithSize:rect.size andDrawingBlock:[[^(CGContextRef c) {
UIImage *image = [UIImage imageNamed:#"FontGradientPink.png"];
CGContextDrawImage(context, rect, [image CGImage]);
} copy] autorelease]];
Ok, I figured it out. Basically, we can override drawRectInText and use our own pattern to color the fill. The advantage of doing this is that we can resize the image into our pattern frame.
First we create a CGPattern object and define a callback to draw the pattern. We also pass the size of the label as a parameter in the callback. We then use the pattern that is drawn in the callback and set it as the fill color of the text:
- (void)drawTextInRect:(CGRect)rect
{
//set gradient as a pattern fill
CGRect info[1] = {rect};
static const CGPatternCallbacks callbacks = {0, &drawImagePattern, NULL};
CGAffineTransform transform = CGAffineTransformMakeScale(1.0, -1.0);
CGPatternRef pattern = CGPatternCreate((void *) info, rect, transform, 10.0, rect.size.height, kCGPatternTilingConstantSpacing, true, &callbacks);
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGFloat alpha = 1.0;
CGColorRef patternColorRef = CGColorCreateWithPattern(patternSpace, pattern, &alpha);
CGColorSpaceRelease(patternSpace);
CGPatternRelease(pattern);
self.textColor = [UIColor colorWithCGColor:patternColorRef];
self.shadowOffset = CGSizeZero;
[super drawTextInRect:rect];
}
The callback draws the image into the context. The image is resized as per the frame size that is passed into the callback.
void drawImagePattern(void *info, CGContextRef context)
{
UIImage *image = [UIImage imageNamed:#"FontGradientPink.png"];
CGImageRef imageRef = [image CGImage];
CGRect *rect = info;
CGContextDrawImage(context, rect[0], imageRef);
}

Using NSImage operation to make a crop effect

I have an NSView that display an image, and i'd like to make this view acts like a cropping image effect. Then i make 3 rectangles (imageRect, secRect and IntersectRect), the imageRect is the rect which show an image, secRect is rect which just act to darken whole imageRect, and the intersectRect is a rect which like an observe rect, what i want to do is like make a "hole" on secRect to see directly into imageRect (without the darken). here's my drawRect method :
- (void)drawRect:(NSRect)rect {
// Drawing code here.
NSImage *image = [NSImage imageNamed:#"Lonely_Tree_by_sican.jpg"];
NSRect imageRect = [self bounds];
[image compositeToPoint:NSZeroPoint operation:NSCompositeSourceOver ];
if (NSIntersectsRect([myDrawRect currentRect], [self bounds])) {
//get the intersectionRect
intersectionRect = NSIntersectionRect([myDrawRect currentRect], imageRect);
//draw the imageRect
[image compositeToPoint:imageRect.origin operation:NSCompositeSourceOver];
//draw the secRect and fill it with black and alpha 0.5
NSRect secRect = NSMakeRect(imageRect.origin.x, imageRect.origin.y, imageRect.size.width, imageRect.size.height);
[[NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:0.5] set];
[NSBezierPath fillRect:secRect];
//have no idea for the intersectRect
/*[image compositeToPoint:intersectionRect.origin
fromRect:secLayer
operation:NSCompositeXOR
fraction:1.0];*/
}
//draw the rectangle
[myDrawRect beginDrawing];
}
I have my own class (myDrawRect) to draw a rectangle based on mouse click on [self bounds], so just ignore the beginDrawing command.
Any help would be fine, thanks. Hebbian.
You're doing far more work than you need to, and you're using deprecated methods (the compositeToPoint:operation: and compositeToPoint:fromRect:operation:fraction: methods) to do it.
All you need to do is send the image a single drawInRect:fromRect:operation:fraction: message. The fromRect: parameter is the rectangle you want to crop to; if you don't want to scale the cropped section, then the destination rect (the drawInRect: parameter) should have the same size.
About the only extra work you may need to do is if the image may be bigger than the view and you want to only draw the section that's within the view's bounds: When that happens, you'll need to inset the crop rectangle by the difference in size between the crop rectangle and the view bounds.

Image Context produces artifacts when compositing UIImages

I'm trying to overlay a custom semi-transparent image over a base image. The overlay image is stretchable and created like this:
[[UIImage imageNamed:#"overlay.png"] stretchableImageWithLeftCapWidth:5.0 topCapHeight:5.0]
Then I pass that off to a method that overlays it onto the background image for a button:
- (void)overlayImage:(UIImage *)overlay forState:(UIControlState)state {
UIImage *baseImage = [self backgroundImageForState:state];
CGRect frame = CGRectZero;
frame.size = baseImage.size;
// create a new image context
UIGraphicsBeginImageContext(baseImage.size);
// get context
CGContextRef context = UIGraphicsGetCurrentContext();
// clear context
CGContextClearRect(context, frame);
// draw images
[baseImage drawInRect:frame];
[overlay drawInRect:frame];// blendMode:kCGBlendModeNormal alpha:1.0];
// get UIImage
UIImage *overlaidImage = UIGraphicsGetImageFromCurrentImageContext();
// clean up context
UIGraphicsEndImageContext();
[self setBackgroundImage:overlaidImage forState:state];
}
The resulting overlaidImage looks mostly correct, it is the correct size, the alpha is blended correctly, etc. however it has vertical artifacts/noise.
UIImage artifacts example http://acaciatreesoftware.com/img/UIImage-artifacts.png
(example at http://acaciatreesoftware.com/img/UIImage-artifacts.png)
I tried clearing the context first and then turning off PNG compression--which reduces the artifacting some (completely on non stretched images I think).
Does anyone know a method for drawing stretchable UIImages with out this sort of artifacting happening?
So the answer is: Don't do this. Instead you can paint your overlay procedurally. Like so:
- (void)overlayWithColor:(UIColor *)overlayColor forState:(UIControlState)state {
UIImage *baseImage = [self backgroundImageForState:state];
CGRect frame = CGRectZero;
frame.size = baseImage.size;
// create a new image context
UIGraphicsBeginImageContext(baseImage.size);
// get context
CGContextRef context = UIGraphicsGetCurrentContext();
// draw background image
[baseImage drawInRect:frame];
// overlay color
CGContextSetFillColorWithColor(context, [overlayColor CGColor]);
CGContextSetBlendMode(context, kCGBlendModeSourceAtop);
CGContextFillRect(context, frame);
// get UIImage
UIImage *overlaidImage = UIGraphicsGetImageFromCurrentImageContext();
// clean up context
UIGraphicsEndImageContext();
[self setBackgroundImage:overlaidImage forState:state];
}
Are you being too miserly with your original image, and forcing it to stretch rather than shrink? I've found best results out of images that fit the same aspect ratio and were reduced in size. Might not solve your problem tho.