简体   繁体   中英

Blurred Screenshot of a view being drawn by UIBezierPath

I'm drawing my graph view using UIBezierPath methods and coretext. I use addQuadCurveToPoint:controlPoint: method to draw curves on graph. I also use CATiledLayer for the purpose of rendering graph with large data set on x axis. I draw my whole graph in an image context and in drawrect: method of my view I draw this image in my whole view. Following is my code.

- (void)drawImage{

        UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0);

        // Draw Curves 
        [self drawDiagonal];

        UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
        [screenshot retain];
        UIGraphicsEndImageContext();
}
- (void)drawRect:(CGRect)rect{

    NSLog(@"Draw iN rect with Bounds: %@",NSStringFromCGRect(rect));
    [screenshot drawInRect:self.frame];
}

However in screenshot the curves drawn between two points are not smooth. I've also set the Render with Antialiasing to YES in my info plist. Please see screenshot. 在此处输入图片说明

We'd have to see how you construct the UIBezierPath , but in my experience, for smooth curves, the key issue is whether the slope of the line between a curve's control point and the end point of that particular segment of the curve is equal to the slope between the next segment of the curve's start point and its control point. I find that easier to draw general smoooth curves using addCurveToPoint rather than addQuadCurveToPoint , so that I can adjust the starting and ending control points to satisfy this criterion more generally.

To illustrate this point, the way I usually draw UIBezierPath curves is to have an array of points on the curve, and the angle that the curve should take at that point, and then the "weight" of the addCurveToPoint control points (ie how far out the control points should be). Thus, I use those parameters to dictate the second control point of a UIBezierPath and the first controlPoint of the next segment of the UIBezierPath . So, for example:

@interface BezierPoint : NSObject
@property CGPoint point;
@property CGFloat angle;
@property CGFloat weight;
@end

@implementation BezierPoint

- (id)initWithPoint:(CGPoint)point angle:(CGFloat)angle weight:(CGFloat)weight
{
    self = [super init];

    if (self)
    {
        self.point  = point;
        self.angle  = angle;
        self.weight = weight;
    }

    return self;
}

@end

And then, an example of how I use that:

- (void)loadBezierPointsArray
{
    // clearly, you'd do whatever is appropriate for your chart.
    // this is just a unclosed loop. But it illustrates the idea.

    CGPoint startPoint = CGPointMake(self.view.frame.size.width / 2.0, 50);

    _bezierPoints = [NSMutableArray arrayWithObjects:
            [[BezierPoint alloc] initWithPoint:CGPointMake(startPoint.x, startPoint.y) 
                                         angle:M_PI_2 * 0.05
                                        weight:100.0 / 1.7],
            [[BezierPoint alloc] initWithPoint:CGPointMake(startPoint.x + 100.0, startPoint.y + 70.0) 
                                         angle:M_PI_2
                                        weight:70.0 / 1.7],
            [[BezierPoint alloc] initWithPoint:CGPointMake(startPoint.x, startPoint.y + 140.0)
                                         angle:M_PI
                                        weight:100.0 / 1.7],
            [[BezierPoint alloc] initWithPoint:CGPointMake(startPoint.x - 100.0, startPoint.y + 70.0)
                                         angle:M_PI_2 * 3.0
                                        weight:70.0 / 1.7],
            [[BezierPoint alloc] initWithPoint:CGPointMake(startPoint.x + 10.0, startPoint.y + 10)
                                         angle:0.0
                                        weight:100.0 / 1.7],
            nil];
}

- (CGPoint)calculateForwardControlPoint:(NSUInteger)index
{
    BezierPoint *bezierPoint = _bezierPoints[index];

    return CGPointMake(bezierPoint.point.x + cosf(bezierPoint.angle) * bezierPoint.weight,  
                       bezierPoint.point.y + sinf(bezierPoint.angle) * bezierPoint.weight);
}

- (CGPoint)calculateReverseControlPoint:(NSUInteger)index
{
    BezierPoint *bezierPoint = _bezierPoints[index];

    return CGPointMake(bezierPoint.point.x - cosf(bezierPoint.angle) * bezierPoint.weight,  
                       bezierPoint.point.y - sinf(bezierPoint.angle) * bezierPoint.weight);
}

- (UIBezierPath *)bezierPath
{
    UIBezierPath *path = [UIBezierPath bezierPath];
    BezierPoint *bezierPoint = _bezierPoints[0];

    [path moveToPoint:bezierPoint.point];

    for (NSInteger i = 1; i < [_bezierPoints count]; i++)
    {
        bezierPoint = _bezierPoints[i];

        [path addCurveToPoint:bezierPoint.point
                controlPoint1:[self calculateForwardControlPoint:i - 1] 
                controlPoint2:[self calculateReverseControlPoint:i]];
    }

    return path;
}

When I render this into a UIImage (using the code below), I don't see any softening of the image, but admittedly the images are not identical. (I'm comparing the image rendered by capture against that which I capture manually with a screen snapshot by pressing power and home buttons on my physical device at the same time.)

If you're seeing some softening, I would suggest renderInContext (as shown below). I wonder if you writing the image as JPG (which is lossy). Maybe try PNG, if you used JPG.

- (void)drawBezier
{
    UIBezierPath *path = [self bezierPath];

    CAShapeLayer *oval = [[CAShapeLayer alloc] init];
    oval.path = path.CGPath;
    oval.strokeColor = [UIColor redColor].CGColor;
    oval.fillColor = [UIColor clearColor].CGColor;
    oval.lineWidth = 5.0;
    oval.strokeStart = 0.0;
    oval.strokeEnd = 1.0;
    [self.view.layer addSublayer:oval];
}

- (void)capture
{
    UIGraphicsBeginImageContextWithOptions(self.view.frame.size, NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.view.layer renderInContext:context];
    UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    // save the image

    NSData *data = UIImagePNGRepresentation(screenshot);
    NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString *imagePath = [documentsPath stringByAppendingPathComponent:@"image.png"];
    [data writeToFile:imagePath atomically:YES];

    // send it to myself so I can look at the file

    NSURL *url = [NSURL fileURLWithPath:imagePath];
    UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[url]
                                                                         applicationActivities:nil];
    [self presentViewController:controller animated:YES completion:nil];
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM