Adding an Image to a Current UIGraphics Context - objective-c

I've got a slideshow that allows users to annotate slides with a simple drawing tool. Just allows you to draw on the screen with your finger and then 'save'. The save feature uses UIImagePNGRepresentation and works rather well. What I need to work out is how to 'continue' existing annotations so when a save happens it also takes into account what is already on the slide.
It works using UIImageContext and saving that image context into a file. When an image is saved it opens onto an overlay UIImageView, so if you are 'continuing' then your drawing onto an existing png file.
Is there a way I can add an existing image to the UIImageContext? Here I control the addition of the lines upon movement:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if(drawToggle){
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
currentPoint.y -= 40;
//Define Properties
[drawView.image drawInRect:CGRectMake(0, 0, drawView.frame.size.width, drawView.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineJoin(UIGraphicsGetCurrentContext(), kCGLineJoinBevel);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 5.0);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0.0, 0.0, 1.0);
//Start Path
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
//Save Path to Image
drawView.image = UIGraphicsGetImageFromCurrentImageContext();
lastPoint = currentPoint;
}
}
Here is the magic saving line:
NSData *saveDrawData = UIImagePNGRepresentation(UIGraphicsGetImageFromCurrentImageContext());
NSError *error = nil;
[saveDrawData writeToFile:dataFilePath options:NSDataWritingAtomic error:&error];
Thanks for any help you can offer.
UPDATE:
Oops I forgot to add, when an annotation is 'saved' the Image Context is ended, so I can't use any Get Current Image Context style methods.

I achieved this by adding this between my Start and End lines:
UIImage *image = [[UIImage alloc] initWithContentsOfFile:saveFilePath];
CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 0, image.size.height);
CGContextScaleCTM(UIGraphicsGetCurrentContext(), 1.0, -1.0);
CGContextDrawImage(UIGraphicsGetCurrentContext(), imageRect, image.CGImage);
The Context Translate and Scales are necessary because converting a UIImage to a CGImage flips the image - it does this because a CGImage draws from a bottom left origin and the UIImage draws from a top left origin, the same co-ordinates on a flipped scale results in a flipped image.
Because I drew the existing image into the UIGraphicsGetCurrentContext() when I save the file it'll take this into account.

Related

Rendering touchesMoved and Fingerpaint slow - iOS7

I have a app that allows fingerprinting. For this I use touchesBegan, touchesMoved, touchesEnded. On iOS6 it works smooth, when moving the finger, the line is painted. But on iOS7, only the first point from touchesBegan is painted and on touchesEnded, the line is painted.
Does anyone has a similar issue and/or solution.
(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
mouseSwiped = YES;
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:drawImage];
// currentPoint.y -= 20; // only for 'kCGLineCapRound'
UIGraphicsBeginImageContext(drawImage.frame.size);
[drawImage.image drawInRect:CGRectMake(0, 0, drawImage.frame.size.width, drawImage.frame.size.height)]; //originally self.frame.size.width, self.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound); //kCGLineCapSquare, kCGLineCapButt, kCGLineCapRound
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 30.0); // for size
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0.0, 0.0, 1.0, 1.0); //values for R, G, B, and Alpha
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
drawImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
lastPoint = currentPoint;
mouseMoved++;
if (mouseMoved == 10) {
mouseMoved = 0;
}
}
EDIT and solution!
Don't try to draw or call draw routines inside the touch delegates. The newer devices might support faster and higher resolution touch detection, and thus your app might be getting touch messages way to fast to draw in time, thus choking your UI run loop. Trying to update faster than 60 fps can do that.
Instead save your touch data points somewhere, and then draw later, for instance inside a polling animation loop callback (using CADisplayLink or a repeating timer), outside the touch handler, thus not choking the UI.
Comment from Lawrence Ingraham in Apple Developer Forum
Based on this, I have made the following changes:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
currentPoint = [touch locationInView:self.view];
[self performSelector:#selector(drawFinger) withObject:nil afterDelay:0.0];
}
- (void)drawFinger
{
UIGraphicsBeginImageContext(imgDibujo.frame.size);
[imgDibujo.image drawInRect:CGRectMake(0, 0, imgDibujo.frame.size.width, imgDibujo.frame.size.height)]; //originally self.frame.size.width, self.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound); //kCGLineCapSquare, kCGLineCapButt, kCGLineCapRound
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), delegate.lineAncho); // for size
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), delegate.colorR, delegate.colorG, delegate.colorB, delegate.colorS); //values for R, G, B, and Alpha
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
imgDibujo.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
lastPoint = currentPoint;
}
It works now perfect on all iPad devices. Hope that this is helpfully for you.

RGBStroke - Image pixels instead of color (iOS)

I am implementing a image mask kind of feature in iOS similar to what is offered in the Blender app with two images. Here is my touch move code :-
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:staticBG1];
UIGraphicsBeginImageContext(view.frame.size);
[image_1 drawInRect:CGRectMake(0, 0, view.frame.size.width, view.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 20.0);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0.0, 0.0, 1.0);
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
image_1 = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
lastPoint = currentPoint;
mouseMoved++;
if (mouseMoved == 10)
mouseMoved = 0;
}
Now what I really want is not the bright red line but pixels from another image in those places. Both images are of the same dimensions. How do I do it??
I tried to implement my manual image processing method my pixel access but it was too slow and this will be done in real time.
Is there any alternative to:
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0.0, 0.0, 1.0);
?
Don't draw a colour or a pattern into the path, draw transparency. You need one image in its own layer behind the image that is being wiped out. Create the path as you are now, but instead of setting the colour, set the blend mode to clear (kCGBlendModeClear).
This will remove sections of the image to allow you to see through to the image below.
Replace:
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 1.0, 0.0, 0.0, 1.0);
with:
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);

UIGraphicsGetImageFromCurrentImageContext crop to content and not to bounding box

I have an area where a user can add their signature to an app, to verify an order.
They sign with their touch.
The only thing is, the frame for the signature is large, and if the user were to make a signature very small, when I save the image, I'm left with a wealth of empty space around the image, which, when I add it to an email further down the process can look horrible.
Is there any way, using that I can crop the image to whatever the bounds of the actual content is, and not the bounds of the box itself?
I would imagine the process would involve somehow detecting the content within the space and drawing a CGRect to match it's bounds, before passing this CGRect back to the context? But I'm not sure how to go about doing this in any way shape or form, this really is my first time using CGContext and the Graphics Frameworks.
Here's my signature drawing code:
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:signView];
//Define Properties
[drawView.image drawInRect:CGRectMake(0, 0, drawView.frame.size.width, drawView.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineJoin(UIGraphicsGetCurrentContext(), kCGLineJoinBevel);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 5.0);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0.0, 0.0, 0.0, 1.0);
CGContextSetShouldAntialias(UIGraphicsGetCurrentContext(), true);
CGContextSetAllowsAntialiasing(UIGraphicsGetCurrentContext(), true);
//Start Path
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
//Save Path to Image
drawView.image = UIGraphicsGetImageFromCurrentImageContext();
lastPoint = currentPoint;
}
Thanks for your help if you can offer it.
You can do this with by tracking the min and max touch values.
For example, make some min/max ivars
#interface ViewController () {
CGFloat touchRectMinX_;
CGFloat touchRectMinY_;
CGFloat touchRectMaxX_;
CGFloat touchRectMaxY_;
UIView *demoView_;
}
#end
Heres a demo rect
- (void)viewDidLoad {
[super viewDidLoad];
demoView_ = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
demoView_.backgroundColor = [UIColor redColor];
[self.view addSubview:demoView_];
}
Set them up with impossible values. You'll want to do this for each signature not each stroke, but how you do this is up to you. I suggest a reset on 'clear', assuming you have a clear button.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
touchRectMinX_ = CGFLOAT_MAX;
touchRectMinY_ = CGFLOAT_MAX;
touchRectMaxX_ = CGFLOAT_MIN;
touchRectMaxY_ = CGFLOAT_MIN;
}
Now record the changes
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
touchRectMinX_ = MIN(touchRectMinX_, currentPoint.x);
touchRectMinY_ = MIN(touchRectMinY_, currentPoint.y);
touchRectMaxX_ = MAX(touchRectMaxX_, currentPoint.x);
touchRectMaxY_ = MAX(touchRectMaxY_, currentPoint.y);
// You can use them like this:
CGRect rect = CGRectMake(touchRectMinX_, touchRectMinY_, fabs(touchRectMinX_ - touchRectMaxX_), fabs(touchRectMinY_ - touchRectMaxY_));
demoView_.frame = rect;
NSLog(#"Signature rect is: %#", NSStringFromCGRect(rect));
}
Hope this helps!

How can i create an eraser for this line, in Xcode?

How, i've this code. When the touch moved, the view adds a line.
Now, if i want to create an eraser for this line, how can i do?
Please, answer me early!
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:drawView];
UIGraphicsBeginImageContext(drawView.frame.size);
[drawView.image drawInRect:CGRectMake(0, 0, drawView.frame.size.width, drawView.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brushDimension);
const CGFloat *components = CGColorGetComponents([brushColor CGColor]);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), components[0], components[1], components[2], components[3]);
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
drawView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
lastPoint = currentPoint;
}
If you are looking for an erase function that the user can use touches to erase portion of the line instead of undo provide by RickyTheCoder, you have 2 options.
The first option is use the brush that has the same background color of
the background view so it perceive as line got erased while it
actually just got paint over with the color that is same as the background.
The second option is to use the brush with clear color and set the
blend mode to clear so it erase the line and the background view is still
visible.
if (isErase)
{
CGContextSetLineWidth(currentContext, 10);
CGContextSetStrokeColorWithColor(currentContext, [UIColor clearColor].CGColor);
CGContextSetFillColorWithColor(currentContext, [UIColor clearColor].CGColor);
CGContextSetBlendMode(currentContext, kCGBlendModeClear);
CGContextDrawPath(currentContext, kCGPathStroke);
}
i think this is what you are looking for:
http://soulwithmobiletechnology.blogspot.com/2011/06/redo-undo-in-paint-feature.html

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()