I have a weird problem when using QuartzCore to draw on a view using user's touch. My idea is draw ellipse in touchesBegan and draw some subpath in touchesMoved and touchesEnd. My code here:
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineJoin (context, kCGLineJoinRound);
CGContextSetLineCap (context, kCGLineCapRound);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextAddPath(context, path);
//CGContextStrokePath(context);
CGContextFillPath(context);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (path == nil) {
CGPathRelease(path);
path = CGPathCreateMutable();
}
strokeWidth = 10;
CGFloat halfWidth = strokeWidth / 2;
CGPoint p = [[touches anyObject] locationInView:self];
CGPathAddEllipseInRect(path, NULL, CGRectMake(p.x - halfWidth, p.y - halfWidth, strokeWidth, strokeWidth));
[self setNeedsDisplay];
}
static CGPoint prevLeft, prevRight;
CGPoint midPoint(CGPoint p1, CGPoint p2) {
return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}
CGPoint triPoint(CGPoint p1, CGPoint p2, CGPoint p3) {
return CGPointMake((p1.x + p2.x + p3.x) / 3, (p1.y + p2.y + p3.y) / 3);
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGFloat halfWidth = strokeWidth / 2;
CGPoint p1 = [touch previousLocationInView:self];
CGPoint p2 = [touch locationInView:self];
CGPoint lineVector = CGPointMake(p2.x - p1.x, p2.y - p1.y);
CGFloat len = hypotf(lineVector.x, lineVector.y);
CGPoint normalVector = CGPointMake(- lineVector.y / len, lineVector.x / len);
CGFloat valx = normalVector.x * halfWidth;
CGFloat valy = normalVector.y * halfWidth;
CGPoint left1 = CGPointMake(p1.x + valx, p1.y + valy);
CGPoint right1 = CGPointMake(p1.x - valx, p1.y - valy);
CGPoint left = CGPointMake(p2.x + valx, p2.y + valy);
CGPoint right = CGPointMake(p2.x - valx, p2.y - valy);
prevLeft = midPoint(left1, left);
prevRight = midPoint(right1, right);
CGPathMoveToPoint(path, NULL, left1.x , left1.y);
CGPathAddLineToPoint(path, NULL, left.x, left.y);
CGPathAddLineToPoint(path, NULL, right.x, right.y);
CGPathAddLineToPoint(path, NULL, right1.x, right1.y);
CGPathCloseSubpath(path);
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGFloat halfWidth = strokeWidth / 2;
CGPoint p1 = [touch previousLocationInView:self];
CGPoint p2 = [touch locationInView:self];
CGPoint lineVector = CGPointMake(p2.x - p1.x, p2.y - p1.y);
CGFloat len = hypotf(lineVector.x, lineVector.y);
CGPoint normalVector = CGPointMake(- lineVector.y / len, lineVector.x / len);
CGFloat valx = normalVector.x * halfWidth;
CGFloat valy = normalVector.y * halfWidth;
CGPoint left1 = CGPointMake(p1.x + valx, p1.y + valy);
CGPoint right1 = CGPointMake(p1.x - valx, p1.y - valy);
CGPoint left = CGPointMake(p2.x + valx, p2.y + valy);
CGPoint right = CGPointMake(p2.x - valx, p2.y - valy);
prevLeft = midPoint(left1, left);
prevRight = midPoint(right1, right);
CGPathMoveToPoint(path, NULL, left1.x , left1.y);
CGPathAddLineToPoint(path, NULL, left.x, left.y);
CGPathAddLineToPoint(path, NULL, right.x, right.y);
CGPathAddLineToPoint(path, NULL, right1.x, right1.y);
CGPathCloseSubpath(path);
[self setNeedsDisplay];
}
But the result when drawing touchesBegan and touchesMoved is here:
it look likes the intersect between ellipse and subpath is not fill with the selected color. Does anyone have this problem before? I am appreciated with your help.
Thanks,
Related
I am trying to clear graphics context. It clears perfectly using these 2 ways.
1. clearContextBeforeDrawing
2. CGContextClearRect(context, rect);
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextFillRect(context, rect);
CGContextFlush(context);
But when I draw lines again. The previous lines are mixed up with the new lines with dots appearing. Can anyone please help ?
This is how I am clearing lines
-(IBAction)eraseButton:(id)sender
{
self.viewmainHandwriting.isErase = TRUE;
[self.viewmainHandwriting setBackgroundColor:[UIColor clearColor]];
[self.viewmainHandwriting setNeedsDisplay]; //It calls my drawRect Function
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
if (!self.isErase)
{
CGContextAddPath(context, path);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextStrokePath(context);
self.empty = NO;
}
else
{
CGContextClearRect(context, self.bounds);
path
self.isErase = FALSE;
}
}
The is how I am drawing lines
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
previousPoint1 = [touch previousLocationInView:self];
previousPoint2 = [touch previousLocationInView:self];
currentPoint = [touch locationInView:self];
[self touchesMoved:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
/* check if the point is farther than min dist from previous */
CGFloat dx = point.x - currentPoint.x;
CGFloat dy = point.y - currentPoint.y;
if ((dx * dx + dy * dy) < kPointMinDistanceSquared) {
return;
}
previousPoint2 = previousPoint1;
previousPoint1 = [touch previousLocationInView:self];
currentPoint = [touch locationInView:self];
CGPoint mid1 = midPoint(previousPoint1, previousPoint2);
CGPoint mid2 = midPoint(currentPoint, previousPoint1);
CGMutablePathRef subpath = CGPathCreateMutable();
CGPathMoveToPoint(subpath, NULL, mid1.x, mid1.y);
CGPathAddQuadCurveToPoint(subpath, NULL, previousPoint1.x, previousPoint1.y, mid2.x, mid2.y);
CGRect bounds = CGPathGetBoundingBox(subpath);
CGPathAddPath(path, NULL, subpath);
CGPathRelease(subpath);
CGRect drawBox = bounds;
drawBox.origin.x -= self.lineWidth * 2.0;
drawBox.origin.y -= self.lineWidth * 2.0;
drawBox.size.width += self.lineWidth * 4.0;
drawBox.size.height += self.lineWidth * 4.0;
[self setNeedsDisplayInRect:drawBox];
}
It looks like you don't clear path in the method that clears the whole view (eraseButton ?).
So the path would still contain the whole old drawing. Only those parts would show on which you call setNeedsDisplayInRect: (from within touchesMoved:). You can reveal the old drawing by drawing over it again.
You should clear path (probably an ivar) by just creating a new one in eraseButton:
CGPathRelease(path);
path = CGPathCreateMutable();
In my app, i used SMRotatory wheel for rotation. It is working fine as rotation regarding.
But i want to detect that rotation is clockwise or anti-clockwise??
//My code is as follow:
- (BOOL)continueTrackingWithTouch:(UITouch*)touch withEvent:(UIEvent*)event
{
CGFloat radians = atan2f(container.transform.b, container.transform.a);
// NSLog(#"rad is %f", radians);
CGPoint pt = [touch locationInView:self];
float dx = pt.x - container.center.x;
float dy = pt.y - container.center.y;
float ang = atan2(dy,dx);
float angleDifference = deltaAngle - ang;
// NSLog(#"%f",angleDifference);
if (angleDifference>=0) {
//NSLog(#"positive");
[[NSUserDefaults standardUserDefaults]setValue:#"positive" forKey:#"CheckValue"];
}
else
{
// NSLog(#"negative");
[[NSUserDefaults standardUserDefaults]setValue:#"negative" forKey:#"CheckValue"];
}
container.transform = CGAffineTransformRotate(startTransform, -angleDifference);
bg.transform=CGAffineTransformRotate(startTransform, -angleDifference);
return YES;
}
Try something like this:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint currentTouch1=[touch locationInView:self.superview];
CGPoint currentTouch2=[touch previousLocationInView:self.superview];
CGFloat x = currentTouch2.x - currentTouch1.x;
CGFloat y = currentTouch2.y - currentTouch1.y;
CGFloat dist = sqrtf(x*x + y*y);
CGFloat cx = currentTouch1.x - self.superview.bounds.size.width / 2;
CGFloat cy = currentTouch2.y - self.superview.bounds.size.height / 2;
CGFloat cdist = sqrtf(cx*cx + cy*cy);
CGFloat angle = atan2(dist, cdist);
//NSLog(#"%f",angle);
CGFloat radians = atan2f(super.transform.b, super.transform.a);
mDegrees = radians * (180 / M_PI);
if (mDegrees < 0.0)
{
mDegrees+=360;
}
NSLog(#"Angle is = %f",angle); //positive is clockwise, negative is counterclockwise
CGAffineTransform transform = CGAffineTransformRotate(self.transform, angle);
self.transform = transform;
touch1 = currentTouch1;
touch2 = currentTouch2;
}
Hi friends in my app i want to allow users to resize,rotate and flip an image,
here i have used the below code to resize,
CGFloat angleToRotate ;CGPoint currentTouchPoint,previousTouchPoint; UITouch *touch;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
touch = [[event allTouches] anyObject];
//if (![touch.view isKindOfClass:[vw class]]) {
touchStart = [[touches anyObject] locationInView:self];
kResizeThumbSize=15;
isResizingLR = (self.bounds.size.width - touchStart.x < kResizeThumbSize && self.bounds.size.height - touchStart.y < kResizeThumbSize);
isResizingUL = (touchStart.x <kResizeThumbSize && touchStart.y <kResizeThumbSize);
isResizingUR = (self.bounds.size.width-touchStart.x < kResizeThumbSize && touchStart.y<kResizeThumbSize);
isResizingLL = (touchStart.x <kResizeThumbSize && self.bounds.size.height -touchStart.y <kResizeThumbSize);
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [[event allTouches] anyObject];
currentTouchPoint = [touch locationInView:self.superview];
previousTouchPoint = [touch previousLocationInView:self.superview];
CGPoint touchPoint1 = [[touches anyObject] locationInView:self.superview];
CGPoint previous1=[[touches anyObject]previousLocationInView:self.superview];
CGPoint touchPoint = [[touches anyObject] locationInView:self];
CGPoint previous=[[touches anyObject]previousLocationInView:self];
float deltaWidth = touchPoint.x-previous.x;
float deltaHeight = touchPoint.y-previous.y;
float deltaWidth1 = currentTouchPoint.x-previousTouchPoint.x;
float deltaHeight1 = currentTouchPoint.y-previousTouchPoint.y;
CGFloat newW= self.superview.frame.size.width+deltaWidth1;
if (isResizingLR) {
self.superview.frame = CGRectMake(self.superview.frame.origin.x, self.superview.frame.origin.y,self.superview.frame.size.width+deltaWidth1,self.superview.frame.size.height+deltaHeight1);
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y,self.frame.size.width+deltaWidth,self.frame.size.height+deltaHeight);
[self setNeedsDisplayInRect:CGRectMake(self.frame.origin.x, self.frame.origin.y,self.frame.size.width+deltaWidth,self.frame.size.height+deltaHeight)];
}
if (isResizingUL) {
self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width - deltaWidth, self.frame.size.height - deltaHeight);
self.superview.frame = CGRectMake(self.superview.frame.origin.x + deltaWidth1, self.superview.frame.origin.y + deltaHeight1, self.superview.frame.size.width - deltaWidth1, self.superview.frame.size.height - deltaHeight1);
[self setNeedsDisplayInRect:CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width - deltaWidth, self.frame.size.height - deltaHeight)];
}
if (isResizingUR) {
self.frame = CGRectMake(self.frame.origin.x ,self.frame.origin.y, self.frame.size.width + deltaWidth, self.frame.size.height - deltaHeight);
self.superview.frame = CGRectMake(self.superview.frame.origin.x ,self.superview.frame.origin.y+deltaHeight1, self.superview.frame.size.width + deltaWidth1, self.superview.frame.size.height - deltaHeight1);
[self setNeedsDisplayInRect:CGRectMake(self.frame.origin.x ,self.frame.origin.y, self.frame.size.width + deltaWidth, self.frame.size.height - deltaHeight)];
}
if (isResizingLL) {
self.frame = CGRectMake(self.frame.origin.x ,self.frame.origin.y , self.frame.size.width - deltaWidth, self.frame.size.height + deltaHeight);
self.superview.frame = CGRectMake(self.superview.frame.origin.x + deltaWidth1 ,self.superview.frame.origin.y , self.superview.frame.size.width - deltaWidth1, self.superview.frame.size.height + deltaHeight1);
[self setNeedsDisplayInRect:CGRectMake(self.frame.origin.x ,self.frame.origin.y , self.frame.size.width - deltaWidth, self.frame.size.height + deltaHeight)];
CGSize supersize=self.superview.frame.size;
NSLog(#"superview frame in resize: %#",NSStringFromCGSize(supersize));
}
if (!isResizingUL && !isResizingLR && !isResizingUR && !isResizingLL) {
CGPoint center = CGPointMake(CGRectGetMidX([self.superview bounds]), CGRectGetMidY([self.superview bounds]));
// [self.superview setContentMode:UIViewContentModeRedraw];
CGFloat angleInRadians = atan2f(currentTouchPoint.y - center.y, currentTouchPoint.x - center.x) - atan2f(previousTouchPoint.y - center.y, previousTouchPoint.x - center.x);
CGFloat angleInRadians1 = atan2f(touchPoint.y - center.y, touchPoint.x - center.x) - atan2f(previous.y - center.y, previous.x - center.x);
[self.superview setTransform:CGAffineTransformRotate([self.superview transform], angleInRadians)];
}
}
Here i can able to resize and rotate but, when i resize AFTER ROTATION the resize is doing somehing plz help me.
use this code for change view size
[UIView animateWithDuration:2
delay:1.0
options: UIViewAnimationCurveEaseOut
animations:^{
// set your resize image;
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
yourview.transform = CGAffineTransformMakeRotation(M_PI_2*1.90);//change 1.90 your transform
[UIView commitAnimations];
I'm creating a paint application but if stroke one line one color and if stroke another line crossing the previous stroke the first stroke change color to the second color
Here is the code I'm using to paint:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
previousPoint1 = [touch previousLocationInView:self];
previousPoint2 = [touch previousLocationInView:self];
currentPoint = [touch locationInView:self];
[self touchesMoved:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
/* check if the point is farther than min dist from previous */
CGFloat dx = point.x - currentPoint.x;
CGFloat dy = point.y - currentPoint.y;
if ((dx * dx + dy * dy) < kPointMinDistanceSquared) {
return;
}
previousPoint2 = previousPoint1;
previousPoint1 = [touch previousLocationInView:self];
currentPoint = [touch locationInView:self];
CGPoint mid1 = midPoint(previousPoint1, previousPoint2);
CGPoint mid2 = midPoint(currentPoint, previousPoint1);
CGMutablePathRef subpath = CGPathCreateMutable();
CGPathMoveToPoint(subpath, NULL, mid1.x, mid1.y);
CGPathAddQuadCurveToPoint(subpath, NULL, previousPoint1.x, previousPoint1.y, mid2.x, mid2.y);
CGRect bounds = CGPathGetBoundingBox(subpath);
CGPathAddPath(path, NULL, subpath);
CGPathRelease(subpath);
CGRect drawBox = bounds;
drawBox.origin.x -= self.lineWidth * 2.0;
drawBox.origin.y -= self.lineWidth * 2.0;
drawBox.size.width += self.lineWidth * 4.0;
drawBox.size.height += self.lineWidth * 4.0;
[self setNeedsDisplayInRect:drawBox];
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context, path);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, self.lineWidth);
CGContextSetStrokeColorWithColor(context, self.lineColor.CGColor);
CGContextStrokePath(context);
self.empty = NO;
}
Any of you knows what I'm doing wrong or how can I keep each stroke independent one from each other ?
I'll really appreciate your help.
I think what you need to do is to start a new path when you change color. Don't just keep adding new subpaths with the new color to the same path that had the old color.
You will need to store for each path the color it should be rendered and then loop over your paths in the drawRect method.
I have this code to paint with my finger on ipad in bitmap context. And i'm getting lined (long dots) line instead of full line and the line is flickering when i move my finger, and it doubled in sense that when i move my finger i get two lines on each side of the screen. Any idea what is going on?
Thanks in advance.
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
self.prePreviousPoint = self.previousPoint;
self.previousPoint = [touch previousLocationInView:self];
CGPoint currentPoint = [touch locationInView:self];
// calculate mid point
CGPoint mid1 = [self calculateMidPointForPoint:self.previousPoint andPoint:self.prePreviousPoint];
CGPoint mid2 = [self calculateMidPointForPoint:currentPoint andPoint:self.previousPoint];
//inicijaliziranje contexta u kojem se sve crta
UIGraphicsBeginImageContext(self.drawImageView.frame.size);
// CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
context = CGBitmapContextCreate(NULL, self.drawImageView.frame.size.width, self.drawImageView.frame.size.height, 8, self.drawImageView.frame.size.width*4, colorSpace,kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
UIGraphicsPushContext(context);
CGRect rect = CGRectMake(0, 0, self.drawImageView.frame.size.width, self.drawImageView.frame.size.height);
// setting line color
[[self currentColor] setStroke];
CGContextSetAllowsAntialiasing(context, true);
CGContextSetShouldAntialias(context, true);
//inicijaliziranje viewa u koji se crta slika te odredivanje vrha linije i biljezenje pokreta
[self.drawImageView.image drawInRect:rect];
CGContextMoveToPoint(context, mid1.x, mid1.y);
// Use QuadCurve is the key
CGContextAddQuadCurveToPoint(context, self.previousPoint.x, self.previousPoint.y, mid2.x, mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);
// complete distance variable is used for setting line width in respect of speed of finger movment and time the finger moves
CGFloat xDist = (previousPoint.x - currentPoint.x); //[2]
CGFloat yDist = (previousPoint.y - currentPoint.y); //[3]
CGFloat distance = sqrt((xDist * xDist) + (yDist * yDist)); //[4]
distance = distance / 10;
if (distance > 10) {
distance = 10.0;
}
distance = distance / 10;
distance = distance * 3;
if (4.0 - distance > self.lineWidth) {
lineWidth = lineWidth + 0.3;
} else {
lineWidth = lineWidth - 0.3;
}
CGContextSetLineWidth(context, self.lineWidth);
// CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 1.0);
NSLog(#"%#",context);
//Crtanje svega na ekran i "zatvaranje" image contexta
//CGContextDrawImage(context,CGRectMake(0, 0, self.drawImageView.frame.size.width, self.drawImageView.frame.size.height) , image1);
CGContextStrokePath(context);
CGImageRef image = CGBitmapContextCreateImage(context);
image2 = [UIImage imageWithCGImage:image];
//UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
self.drawImageView.image = image2;
UIGraphicsPopContext();
CGImageRelease(image);
NSLog(#"%# %#",image,image2);
CGContextRelease(context);
UIGraphicsEndImageContext();
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
colorSpace = CGColorSpaceCreateDeviceRGB();
context1 = CGBitmapContextCreate(NULL, self.drawImageView.frame.size.width, self.drawImageView.frame.size.height, 8, self.drawImageView.frame.size.width*4, colorSpace,kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
self.previousPoint = [touch locationInView:self];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
self.prePreviousPoint = self.previousPoint;
self.previousPoint = [touch previousLocationInView:self];
CGPoint currentPoint = [touch locationInView:self];
// calculate mid point
CGPoint mid1 = [self calculateMidPointForPoint:self.previousPoint andPoint:self.prePreviousPoint];
CGPoint mid2 = [self calculateMidPointForPoint:currentPoint andPoint:self.previousPoint];
rect = CGRectMake(0, 0, self.drawImageView.frame.size.width, self.drawImageView.frame.size.height);
UIGraphicsPushContext(context1);
UIGraphicsBeginImageContext(self.drawImageView.frame.size);
context = UIGraphicsGetCurrentContext();
// setting line color
[[self currentColor] setStroke];
CGContextSetAllowsAntialiasing(context, true);
CGContextSetShouldAntialias(context, true);
[self.drawImageView.image drawInRect:rect];
CGContextMoveToPoint(context, mid1.x, mid1.y);
// Use QuadCurve is the key
CGContextAddQuadCurveToPoint(context, self.previousPoint.x, self.previousPoint.y, mid2.x, mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);
// complete distance variable is used for setting line width in respect of speed of finger movment and time the finger moves
CGFloat xDist = (previousPoint.x - currentPoint.x); //[2]
CGFloat yDist = (previousPoint.y - currentPoint.y); //[3]
CGFloat distance = sqrt((xDist * xDist) + (yDist * yDist)); //[4]
distance = distance / 10;
if (distance > 10) {
distance = 10.0;
}
distance = distance / 10;
distance = distance * 3;
if (4.0 - distance > self.lineWidth) {
lineWidth = lineWidth + 0.3;
} else {
lineWidth = lineWidth - 0.3;
}
CGContextSetLineWidth(context, 15);
// CGContextSetRGBStrokeColor(context, 0.0, 0.0, 0.0, 1.0);
CGContextStrokePath(context);
image = CGBitmapContextCreateImage(context);
image2 = [UIImage imageWithCGImage:image];
UIGraphicsPopContext();
UIGraphicsEndImageContext();
CGContextRelease(context1);
CGImageRelease(image);
[drawImageView setImage:image2];
}
This is the entire code that solved my problem i figured it out on my own at the end.