I would like to know if you guys have any idea of how I could possibly draw a sketchy-looking line on an iOS app.
Currently I am drawing thanks to a UIBezierPath which I then render into a bitmap graphics context. How could I make the line look sketchy?
// Produce and add points to draw in the UIBezierPath
// Draw the path into the Bitmap context
UIGraphicsBeginImageContext(self.bounds.size);
[frameBuffer drawAtPoint:CGPointMake(0, 0) blendMode:kCGBlendModeCopy alpha:1.0];
[[datasource lineStrokeColorForSketchCanvas:self] setStroke];
[curvePath stroke];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Thank ou very much in advance :)
I would suggest using a small batch of "sketchy" line textures, and then using those + the UIBezierPath to place the sketchy images along the path at the correct rotation.
You could draw those to the context, and save them so that you don't have to generate them every single time.
Elaboration:
Start at one end of your UIBezierPath, every x points down the path, draw a sketchy line section centered on that point, keep going until you get to the end of the path.
Related
I need to implement this functionality.Please suggest me.
It's not working properly means it is taking the end angle for the filling colour but here mentioned the "fromValue" and "toValue" but its not going through the fromValue and toValue.
Please anyone can edit my code.
Thanks in advance.
CAShapeLayer *circle=[CAShapeLayer layer];
circle.path=[UIBezierPath bezierPathWithArcCenter:CGPointMake(self.img_View.frame.origin.x, self.img_View.frame.origin.y) radius:50 startAngle:0 endAngle:90 clockwise:YES].CGPath;
circle.fillColor=[UIColor clearColor].CGColor;
circle.strokeColor=[UIColor greenColor].CGColor;
circle.lineWidth=16;
CABasicAnimation *animation=[CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration=10;
animation.removedOnCompletion=NO;
// animation.fromValue=#(0);
animation.fromValue=[NSNumber numberWithInt:0];
animation.toValue=[NSNumber numberWithInt:20];
animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[circle addAnimation:animation forKey:#"drawCircleAnimation"];
[img_View.layer.sublayers makeObjectsPerformSelector:#selector(removeFromSuperlayer)];
[img_View.layer addSublayer:circle];
you can do it with UIBezierPath it is very efficient to draw shapes.
The bezier path you use as a clip seems to be just a fraction of a circle, while in the image you show, the path is more complex : 2 fractions of a circle, linked by 2 lines, the whole path having a 'ring' shape.
This approach should work, I used it for a timer with the same kind of look. Although I didn't used directly AngleGradientLayer, I modified its - (CGImageRef)newImageGradientInRect:(CGRect)rect method to return a UIImage. But I had to rotate this image by + PI/2, as Pavlov gradient angular gradient starts horizontally.
I use a UIImage, because it's a background that DOESN'T change, so I saved an instance of this UIImage in my layer, and draw it whenever I update the clipping path
- (void)drawInContext:(CGContextRef)ctx {
UIBezierPath *currentPath = [self timerPath];
// other drawing code for glow (shadow) and white stroke)
CGContextAddPath(ctx, currentPath.CGPath);
// clip !
CGContextClip(ctx);
CGContextDrawImage(ctx, self.bounds, _circularGradientImage.CGImage);
//_circularGradientImage from modified newImageGradientInRect method.
}
- (void)drawRect:(CGRect)rect{
float sliceSize = rect.size.width / imagesShownAtOnce;
//Apply our clipping region and fill it with black
[clippingRegion addClip];
[clippingRegion fill];
//Draw the 3 images (+1 for inbetween), with our scroll amount.
CGPoint loc;
for (int i=0;i<imagesShownAtOnce+1;i++){
loc = CGPointMake(rect.origin.x+(i*sliceSize)-imageScroll, rect.origin.y);
[[buttonImages objectAtIndex:i] drawAtPoint:loc];
}
//Draw the text region background
[[UIColor blackColor] setFill];
[textRegion fillWithBlendMode:kCGBlendModeNormal alpha:0.4f];
//Draw the actual text.
CGRect textRectangle = CGRectMake(rect.origin.x+16,rect.origin.y+rect.size.height*4/5.6,rect.size.width/1.5,rect.size.height/3);
[[UIColor whiteColor] setFill];
[buttonText drawInRect:textRectangle withFont:[UIFont fontWithName:#"Avenir-HeavyOblique" size:22]];
}
clippingRegion and textRegion are UIBezierPaths to give me the rounded rectangles I want (First for a clipping region, 2nd as an overlay for my text)
The middle section is drawing 3 images and letting them scroll along, which im updating every 2 refreshes from a CADisplayLink, and that invalidates the draw region by calling [self setNeedsDisplay], and also increasing my imageScroll variable.
Now that that background information is done, here is my issue:
It runs, and even runs smoothly. But it is using up an absolutely high amount of CPU time (80%+)!! How do I push this off to the GPU on the phone instead? Someone told me about CALayers but I've never dealt with them before
Draw each component of your drawing once into something (a view or layer) and let it hold the cached the drawing. Then you just move or transform each component, and exactly as you say, it's all done by the GPU.
You could do this with individual views or with individual layers, but that doesn't really matter (a view is a layer, under the hood). The point is that there is no need to be constantly redrawing from scratch when all you really want is to move the same persistent pieces around.
Learning about CALayer would be a good idea, as it is in fact the basis of all drawing on iOS. What could be more important to know about than that?
How to divide this UIImage by the black line into two parts. The upper contour set of UIBezierPath.
I need to get two resulting UIImages. So is it possible?
The following set of routines create versions of a UIImage with either only the content inside a path, or only content outside that path.
Both make use of the compositeImage method, which uses CGBlendMode. CGBlendMode is very powerful for masking anything you can draw against anything else you can draw. Calling compositeImage: with other blend modes can have interesting (if not always useful) effects. See the CGContext Reference for all the modes.
The clipping method I described in my comment to your OP does work and is probably faster, but only if you have UIBezierPaths defining all the regions you want to clip.
- (UIImage*) compositeImage:(UIImage*) sourceImage onPath:(UIBezierPath*) path usingBlendMode:(CGBlendMode) blend;
{
// Create a new image of the same size as the source.
UIGraphicsBeginImageContext([sourceImage size]);
// First draw an opaque path...
[path fill];
// ...then composite with the image.
[sourceImage drawAtPoint:CGPointZero blendMode:blend alpha:1.0];
// With drawing complete, store the composited image for later use.
UIImage *maskedImage = UIGraphicsGetImageFromCurrentImageContext();
// Graphics contexts must be ended manually.
UIGraphicsEndImageContext();
return maskedImage;
}
- (UIImage*) maskImage:(UIImage*) sourceImage toAreaInsidePath:(UIBezierPath*) maskPath;
{
return [self compositeImage:sourceImage onPath:maskPath usingBlendMode:kCGBlendModeSourceIn];
}
- (UIImage*) maskImage:(UIImage*) sourceImage toAreaOutsidePath:(UIBezierPath*) maskPath;
{
return [self compositeImage:sourceImage onPath:maskPath usingBlendMode:kCGBlendModeSourceOut];
}
I tested clipping, and in a few different tests it was 25% slower than masking to achieve the same result as the [maskImage: toAreaInsidePath:] method in my other answer. For completeness I include it here, but please don't use it without a good reason.
- (UIImage*) clipImage:(UIImage*) sourceImage toPath:(UIBezierPath*) path;
{
// Create a new image of the same size as the source.
UIGraphicsBeginImageContext([sourceImage size]);
// Clipping means drawing only happens within the path.
[path addClip];
// Draw the image to the context.
[sourceImage drawAtPoint:CGPointZero];
// With drawing complete, store the composited image for later use.
UIImage *clippedImage = UIGraphicsGetImageFromCurrentImageContext();
// Graphics contexts must be ended manually.
UIGraphicsEndImageContext();
return clippedImage;
}
This can be done but it requires some trigonometry. Let's consider the case for the upper image. First, determine the bottommost end point of the UIBezierPath and use UIGraphicsBeginImageContext to get the top part of the image above the line. This will look as follows:
Now, assuming that your line is straight, move pixel by pixel along the line drawing vertical strokes of clearColor (loop for top portion. Proceed on similar lines for bottom portion):
for(int currentPixel_x=0;currentPixel_x<your_ui_image_top.size.width)
UIGraphicsBeginImageContext(your_ui_image_top.size);
[your_ui_image_top drawInRect:CGRectMake(0, 0, your_ui_image_top.size.width, your_ui_image_top.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 1.0);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(),kCGBlendModeClear);
CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(),[UIColor clearColor].CGColor);
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), currentPixel_x, m*currentPixel_x + c);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPixel_x, your_ui_image_top.size.height);
CGContextStrokePath(UIGraphicsGetCurrentContext());
your_ui_image_top = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
Your UIBezierPath will have to be converted to a straight line of the form y = m*x + c. The x in this equation will be currentPixel_x above. Iterate through the width of the image, increasingcurrentPixel_x by 1 each time. next_y_point_on_your_line will be calculated as:
next_y_point_on_your_line = m*currentPixel_x + c
Each vertical stroke will be 1 pixel wide and its height will depend on how you traverse through them. After some iterations, your image will look roughly (please excuse my poor photo-editing skills!) like:
There are multiple ways of how you draw the clear strokes and this is just one way of going about it. You can also have clear strokes that are parallel to the given path if it gives better results.
Another way is to set the alpha of the pixels below the line to 0.
I'm making a UISlider from scratch. I started by making a rounded rectangle, which I did using the code below:
CGRect frame = CGRectMake(10, 10, self.frame.size.width, 10);
UIBezierPath* path = [UIBezierPath bezierPathWithRoundedRect:frame cornerRadius:10.0];
[[UIColor blueColor] setFill];
[path fill];
I saw some other options to make a rounded rectangle but thought this was the quickest way. Are there any limitations with making one using UIBezierPath? Namely, the slider needs to be able to move upon touch events, so I want to change the center property of a BezierPath. Is this possible?
You would need to either recreate the bezier path each time you need to change the slider position, or use CGContext's transform matrix to draw it in a different place.
I suggest you look at using a CALayer for the moving part of the slider. Draw the channel of the slider in view.layer, and add a sublayer in which you draw the "thumb" of the slider. Then you can just reposition the thumb layer when you need to move it.
I have a window with an subclass of NSView in it. Inside the view, I put an NSImage.
I want to be able to rotate the image by 90 degrees, keeping the (new) upper left corner of the image in the upper left corner of the view. Of course, I will have to rotate the image, and then translate it to put the origin back into place.
In Carbon, I found CGContextRotateCTM which does what I want . However, I can't find the right call in ObjC. setFrameCenterRotation doesn't seem to do anything, and in setFrameRotation, I can't seem to figure out where the origin is, so I can approprately translate.
It seems to move. When I resize the window it puts the image (or part of it, I seem to have a strange clipping issue as wel) and when I scroll, it jumps to a different (and not always the saem) location.
Does this make sense to anyone?
thanks
I rotate text on the screen for an app I work on and the Cocoa (I assume you mean Cocoa and not ObjC in your question) way of doing this is to use NSAffineTransform.
Here's a snippet that should get you started
double rotateDeg = 90;
NSAffineTransform *rotate = [[NSAffineTransform alloc] init];
NSGraphicsContext *context = [NSGraphicsContext currentContext];
[context saveGraphicsState];
[rotate rotateByDegrees:rotateDeg];
[rotate concat];
/* Your drawing code [NSImage drawAtPoint....]for the image goes here
Also, if you need to lock focus when drawing, do it here. */
[rotate release];
[context restoreGraphicsState];
The mathematics on the rotation can get a little tricky here because what the above does is to rotate the coordinate system that you are drawing into. My rotation of 90 degrees is a counter-clockwise rotation.