How to rotate CGContext about center? - objective-c

I have the following code to create a multi-colored ImageContext:
UIGraphicsBeginImageContext(self.view.frame.size);
[self.imageToDelete drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGColorRef mycolor;
mycolor = [[UIColor blueColor] CGColor];
CGPoint mid = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), mid.x, mid.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), mid.x, mid.y+50);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(),3);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeCopy);
CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), mycolor);
CGContextStrokePath(UIGraphicsGetCurrentContext());
CGContextFlush(UIGraphicsGetCurrentContext());
mycolor = [[UIColor whiteColor] CGColor];
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), mid.x, mid.y+50);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), mid.x, mid.y+75);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(),3);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeCopy);
CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), mycolor);
CGContextStrokePath(UIGraphicsGetCurrentContext());
CGContextFlush(UIGraphicsGetCurrentContext());
mycolor = [[UIColor cyanColor] CGColor];
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), mid.x, mid.y+75);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), mid.x, mid.y+100);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(),3);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeCopy);
CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), mycolor);
CGContextStrokePath(UIGraphicsGetCurrentContext());
CGContextFlush(UIGraphicsGetCurrentContext());
self.imageToDelete = UIGraphicsGetImageFromCurrentImageContext();
[self.mainImage drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) blendMode:kCGBlendModeCopy alpha:1.0];
[self.imageToDelete drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) blendMode:kCGBlendModeNormal alpha:1];
self.mainImage = UIGraphicsGetImageFromCurrentImageContext();
SKSpriteNode *node = [SKSpriteNode spriteNodeWithTexture:[SKTexture textureWithImage:self.mainImage]];
node.zPosition = 100;
node.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
node.size = self.mainImage.size;
[self addChild:node];
UIGraphicsEndImageContext();
This is the result:
I would like to rotate this line about the center of the screen (or the top of this line), and keep the original line as well. Is there a way to make a copy, and rotate it about the center point? To clarify, mid is a CGPoint defined as (self.view.frame.size.width/2,self.view.frame.size.height/2), and this is done in Sprite-Kit, iOS, Objective-C.

Rotation is around the origin. To rotate around another point, translate that point to the origin, rotate, and translate back.

This supplements #matt's answer with an example (with Swift 5, iOS 16), ... a function added to a subclass of UIView, where the view is comprised of sublayers of CAShapeLayer derived from UIBezierPath.
As Matt indicated, CGContext rotation centers at origin, therefore one must
translate center of view to origin, do the rotation, then translate back. Note, also the conversion of degrees to radians in the parameter of rotate().
func asImage(rotationDegrees: CGFloat) -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
let image = renderer.image(actions: { _ in
if let ctx = UIGraphicsGetCurrentContext() {
ctx.translateBy(x: bounds.size.width / 2, y: bounds.size.height / 2)
ctx.rotate(by: rotationDegrees * .pi / 180)
ctx.translateBy(x: -bounds.size.width / 2, y: -bounds.size.height / 2)
for sublayer in self.layer.sublayers!.reversed() {
sublayer.render(in: ctx)
}
}
})
return image
}

Related

Core graphics drawing stretches when orientation changes

I am making an app that allows you to draw, and it is working well in portrait mode. However, when I rotate the screen, the drawn image becomes distorted (squashed and stretched). What would I need to do to make it retain the image's dimensions even though the UIImageView has changed its dimensions?
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
mouseSwiped = NO;
UITouch *touch = [touches anyObject];
lastPoint = [touch locationInView:self.view]; // first point touched
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
mouseSwiped = YES;
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view]; // second point touched (to be connected to first point)
// initialize the UIImageView that will be drawn on
UIGraphicsBeginImageContext(self.view.frame.size);
//UIGraphicsBeginImageContextWithOptions(self.view.frame.size, NO, 0.0); // use this instead of previous line to make smoother drawings
if (!erasing) // choose tempDrawImage if not erasing
[self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
else // choose mainImage if erasing
[self.mainImage.image drawInRect:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
// draw a line with CGContextAddLineToPoint from lastPoint to currentPoint
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
if (!erasing) { // color selected
// set brush size, opacity, and stroke color
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush );
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, 1.0);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeNormal);
// draw the path
CGContextStrokePath(UIGraphicsGetCurrentContext());
// draw on the tempDrawImage UIImageView
self.tempDrawImage.image = UIGraphicsGetImageFromCurrentImageContext();
[self.tempDrawImage setAlpha:opacity];
}
else { // eraser selected
// set brush size
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeClear);
// draw the path
CGContextStrokePath(UIGraphicsGetCurrentContext());
// draw on the tempDrawImage UIImageView
self.mainImage.image = UIGraphicsGetImageFromCurrentImageContext();
//[self.mainImage setAlpha:1.0];
}
UIGraphicsEndImageContext();
lastPoint = currentPoint;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// if screen was not swiped (i.e. the screen was only tapped), draw a single point
if(!mouseSwiped) {
UIGraphicsBeginImageContext(self.view.frame.size);
//UIGraphicsBeginImageContextWithOptions(self.view.frame.size, NO, 0.0); // use this instead of previous line to make smoother drawings
if (!erasing) {
[self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, opacity);
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
CGContextFlush(UIGraphicsGetCurrentContext());
self.tempDrawImage.image = UIGraphicsGetImageFromCurrentImageContext();
}
else {
[self.mainImage.image drawInRect:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeClear);
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
CGContextFlush(UIGraphicsGetCurrentContext());
self.mainImage.image = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext();
}
///////BRUSH STROKE IS DONE; BEGIN UIIMAGEVIEW MERGE//////
if (!erasing) {
// initialize mainImage (for merge)
UIGraphicsBeginImageContext(self.mainImage.frame.size);
// merge tempDrawImage with mainImage
[self.mainImage.image drawInRect:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height) blendMode:kCGBlendModeNormal alpha:1.0];
[self.tempDrawImage.image drawInRect:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height) blendMode:kCGBlendModeNormal alpha:opacity];
self.mainImage.image = UIGraphicsGetImageFromCurrentImageContext();
// clear tempDrawImage
self.tempDrawImage.image = nil;
}
UIGraphicsEndImageContext();
}

CoreGraphics draw an image on a white canvas

I have a source image which has a variable width and height, which I must show on a fullscreen iPad UIImageView but with addition of borders around the image itself. So my task is to create a new image with a white border around it, but not overlapping on the image itself. I'm currently doing it with overlapping via this code:
- (UIImage*)imageWithBorderFromImage:(UIImage*)source
{
CGSize size = [source size];
UIGraphicsBeginImageContext(size);
CGRect rect = CGRectMake(0, 0, size.width, size.height);
[source drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextSetLineWidth(context, 40.0);
CGContextStrokeRect(context, rect);
UIImage *testImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return testImg;
}
Can anyone tell me how do I first draw a white canvas that is 40 pixels bigger in each direction than the source image and then draw that image on it?
I adjusted your code to make it work. Basically what it does:
Set canvas size to the size of your image + 2*margins
Fill the whole canvas with background color (white in your case)
Draw your image in the appropriate rectangle (with initial size and required margins)
Resulting code is:
- (UIImage*)imageWithBorderFromImage:(UIImage*)source
{
const CGFloat margin = 40.0f;
CGSize size = CGSizeMake([source size].width + 2*margin, [source size].height + 2*margin);
UIGraphicsBeginImageContext(size);
[[UIColor whiteColor] setFill];
[[UIBezierPath bezierPathWithRect:CGRectMake(0, 0, size.width, size.height)] fill];
CGRect rect = CGRectMake(margin, margin, size.width-2*margin, size.height-2*margin);
[source drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0];
UIImage *testImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return testImg;
}

UIGraphicsGetImageFromCurrentImageContext counterpart in desktop Cocoa?

I'm trying to crate a simple Mac paint application. On iOS I used UIGraphicsGetImageFromCurrentImageContext to update a UIImageView's image when touchesMoved. I'm now trying to active the same thing but on a Mac app, I want to do:
myNSImageView.image = UIGraphicsGetImageFromCurrentImageContext();
But that function does only exist on cocoa-touch and not within the cocoa framework, any tips on where/if I can find a similar function in Cocoa?
Thanks.
Update
Some additional code for completeness.
- (void)mouseDragged:(NSEvent *)event
{
NSInteger width = canvasView.frame.size.width;
NSInteger height = canvasView.frame.size.height;
NSGraphicsContext * graphicsContext = [NSGraphicsContext currentContext];
CGContextRef contextRef = (CGContextRef) [graphicsContext graphicsPort];
CGContextRef imageContextRef = CGBitmapContextCreate(0, width, height, 8, width*4, [NSColorSpace genericRGBColorSpace].CGColorSpace, kCGImageAlphaPremultipliedFirst);
NSPoint currentPoint = [event locationInWindow];
CGContextSetRGBStrokeColor(contextRef, 49.0/255.0, 147.0/255.0, 239.0/255.0, 1.0);
CGContextSetLineWidth(contextRef, 1.0);
CGContextBeginPath(contextRef);
CGContextMoveToPoint(contextRef, lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(contextRef, currentPoint.x, currentPoint.y);
CGContextDrawPath(contextRef,kCGPathStroke);
CGImageRef imageRef = CGBitmapContextCreateImage(imageContextRef);
NSImage* image = [[NSImage alloc] initWithCGImage:imageRef size:NSMakeSize(width, height)];
canvasView.image = image;
CFRelease(imageRef);
CFRelease(imageContextRef);
lastPoint = currentPoint;
}
The result I'm getting now that the painting works fine in the upper part of the view, but bot the rest. Like on this image (the red part should be the canvasView):
NSInteger width = 1024;
NSInteger height = 768;
CGContextRef contextRef = CGBitmapContextCreate(0, width, height, 8, width*4, [NSColorSpace genericRGBColorSpace].CGColorSpace, kCGImageAlphaPremultipliedFirst);
CGContextSetRGBStrokeColor(contextRef, 49.0/255.0, 147.0/255.0, 239.0/255.0, 1.0);
CGContextFillRect(contextRef, CGRectMake(0.0f, 0.0f, width, height));
CGImageRef imageRef = CGBitmapContextCreateImage(contextRef);
NSImage* image = [[NSImage alloc] initWithCGImage:imageRef size:NSMakeSize(width, height)];
CFRelease(imageRef);
CFRelease(contextRef);
NSGraphicsContext * graphicsContext = [NSGraphicsContext currentContext];
CGContextRef contextRef = (CGContextRef) [graphicsContext graphicsPort];
CGContextRef imageContextRef = CGBitmapContextCreate(0, width, height, 8, width*4, [NSColorSpace genericRGBColorSpace].CGColorSpace, kCGImageAlphaPremultipliedFirst);
You're assuming, first off, that there is a current context, and then you create a separate context.
CGContextSetRGBStrokeColor(contextRef, 49.0/255.0, 147.0/255.0, 239.0/255.0, 1.0);
CGContextSetLineWidth(contextRef, 1.0);
(etc.)
And then you draw into the context you assume exists, not the context you created.
CGImageRef imageRef = CGBitmapContextCreateImage(imageContextRef);
And then you create an image from the context you created and never drew into.
Cut out the attempt to get [NSGraphicsContext currentContext] and its graphics port. You can't assume that there is a current context outside of drawRect:, anyway. Instead, draw into the context you created.

Slightly-transparent white line around UIImage after adding overlays?

I have a function that will overlay images over another image and return as a UIImage. However, a white line-ish thing comes up on it. Here's what I have
- (UIImage * ) romanticFilterImage: (UIImage *) imageA {
UIImage *vintageOne = [UIImage imageNamed:#"filter_vintage_1.png"];
UIImage *vintageTwo= [UIImage imageNamed:#"filter_vintage_2.png"];
UIImage *vintageThree = [UIImage imageNamed:#"filter_vintage_3.png"];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(imageA.size.width, imageA.size.height), YES, 0.0);
[imageA drawAtPoint: CGPointMake(0,0)];
[vintageThree drawInRect:CGRectMake(0, 0, imageA.size.width, imageA.size.height) blendMode: kCGBlendModeMultiply alpha: 1.0];
[vintageTwo drawInRect:CGRectMake(0, 0, imageA.size.width + 2, imageA.size.height + 2) blendMode: kCGBlendModeColorBurn alpha: 1.0];
[vintageOne drawInRect:CGRectMake(0, 0, imageA.size.width, imageA.size.height) blendMode: kCGBlendModeMultiply alpha: 1.0];
UIImage *source = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGSize size = [source size];
UIGraphicsBeginImageContext(size);
CGRect rect = CGRectMake(0, 0, size.width, size.height);
[source drawInRect:rect blendMode:kCGBlendModeNormal alpha:1.0];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 5.0, 5.0, 5.0, 5.0);
CGContextStrokeRect(context, rect);
UIImage *testImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return testImg;
}
Here is a screenshot: (I have added a black background to enhance the slighly transparent border.
Aren't you stroking the image white in these lines?
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 5.0, 5.0, 5.0, 5.0);
CGContextStrokeRect(context, rect);
UIImage *testImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Although I am not sure if you intended GContextSetRGBStrokeColor(context, 5.0, 5.0, 5.0, 5.0); as 1.0 is the maximum value for the rgba values in the function.

How to erase some portion of a UIImageView's image on iOS?

I have a view with UIImageView and an image set to it. I want to erase the image as something like we do in photoshop with an eraser. How do I achieve this? Also, how do I undo erase?
If you know what area you want to erase, you can create a new image of the same size, set the mask to the full image minus the area you want to erase, draw the full image into the new image, and use that as the new image. To undo, simply use the previous image.
Edit
Sample code. Say the area you want to erase from image view imgView is specified with by erasePath:
- (void) clipImage
{
UIImage *img = imgView.image;
CGSize s = img.size;
UIGraphicsBeginImageContext(s);
CGContextRef g = UIGraphicsGetCurrentContext();
CGContextAddPath(g,erasePath);
CGContextAddRect(g,CGRectMake(0,0,s.width,s.height));
CGContextEOClip(g);
[img drawAtPoint:CGPointZero];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
UIGraphicsBeginImageContext(imgBlankView.frame.size);
[imgBlankView.image drawInRect:CGRectMake(0, 0, imgBlankView.frame.size.width, imgBlankView.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(),lineWidth);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), red, green, blue, 1.0);
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextSetShouldAntialias(UIGraphicsGetCurrentContext(), YES);
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint1.x, lastPoint1.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
imgBlankView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Swift 5.2 Version
UIGraphicsBeginImageContextWithOptions(imageView.frame.size, false, 1.0)
let currentContext = UIGraphicsGetCurrentContext()
currentContext?.addPath(shapeLayer.path!)
currentContext?.addRect(CGRect(x: 0, y: 0, width: imageView.frame.size.width, height: imageView.frame.size.height))
currentContext?.clip(using: .evenOdd)
imageView.image?.draw(at: .zero)
let imageTeemp = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext()