Invert a radial gradient mask in core graphics - ios

I am able to mask a picture with black/white radial gradient in iOS using core graphics. But I am getting result just the opposite I want. I want the picture to be transparent from the region where gradient is applied and shall display the remaining part of the picture. That means a "![hole][1]" in the picture. But here the hole will be created by a gradient mask.
Can anyone please suggest me some way or any hint to invert the radial gradient mask, just like we do in photoshop.

Instead of using mask, I think the best way is to insert the layer. That's what I did for my IOS app.
- (void) addSublayer {
CGPoint center = CGPointMake(self.addButton.frame.origin.x, self.addButton.frame.origin.y);
UIBezierPath* clip = [UIBezierPath bezierPathWithArcCenter:center
radius:CGRectGetMaxX(self.addUnavailabilitySubView.frame) - self.addButton.center.x
startAngle:-1.57
endAngle:1.57
clockwise:YES];
[clip addLineToPoint:center];
[clip closePath];
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
[shapeLayer setMasksToBounds:YES];
shapeLayer.frame = self.view.layer.bounds;
shapeLayer.path = clip.CGPath;
//This gradient layer can be a customised one
CALayer *gradientLayer = [CALayer layer];
gradientLayer.frame = CGRectMake(0, 0, self.view.bounds.size.height, self.view.bounds.size.width);
[self.view.layer insertSublayer:gradientLayer atIndex:0];
}
I can also share my radial gradient layer here :)
-(void)drawInContext:(CGContextRef)ctx {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 }; // End color
CGFloat components[8] = {0.f, 0.f, 0.f, 0.3f, 0.f, 0.f, 0.f, 0.8f};
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, num_locations);
CGFloat myStartRadius, myEndRadius;
CGPoint radialCenter = CGPointMake(100, 100)
myStartRadius = 20;
myEndRadius = 300;
CGContextDrawRadialGradient (ctx, gradient, radialCenter,
myStartRadius, radialCenter, myEndRadius,
kCGGradientDrawsAfterEndLocation);
}

Related

How to set gradient color with angle

I want to set gradient on my button, i have to set simple linear gradient(without angle) with two color, but i don't know how to set angle value on gradient
Angle:- 61
below image define psd gradient overlay effect
Thanks in advance
Try it, maybe it will help
- (CAGradientLayer *)gradientLayerWithColors:(NSArray *)colors angle:(CGFloat)angle {
CAGradientLayer *layer = [CAGradientLayer layer];
layer.colors = colors;
CGFloat x = angle / 360.f;
CGFloat a = pow(sin((2*M_PI*((x+0.75)/2))),2);
CGFloat b = pow(sin((2*M_PI*((x+0.0)/2))),2);
CGFloat c = pow(sin((2*M_PI*((x+0.25)/2))),2);
CGFloat d = pow(sin((2*M_PI*((x+0.5)/2))),2);
layer.startPoint = CGPointMake(a, b);
layer.endPoint = CGPointMake(c, d);
return layer;
}
CAGradientLayer *gradientMask = [CAGradientLayer layer];
gradientMask.frame = CGRectMake(0, 0, myView.bounds.size.width, myView.bounds.size.height);
gradientMask.colors = #[(id)[UIColor redColor].CGColor,
(id)[UIColor greenColor].CGColor];
gradientMask.locations = #[#0.0, #0.10, #0.60, #1.0];
[myView.layer insertSublayer:gradientMask atIndex:0];
adjust location value and frame size accordingly.
This is a slightly modified code generated by PaintCode that solves just this problem:
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: self.view.bounds];
UIBezierPath* rectangleRotatedPath = [rectanglePath copy];
CGAffineTransform transform = CGAffineTransformMakeRotation(yourAngleInRadians);
[rectangleRotatedPath applyTransform: transform];
CGRect rectangleBounds = CGPathGetPathBoundingBox(rectangleRotatedPath.CGPath);
transform = CGAffineTransformInvert(transform);
CGPoint startPoint = CGPointApplyAffineTransform(CGPointMake(CGRectGetMinX(rectangleBounds), CGRectGetMidY(rectangleBounds)), transform);
CGPoint endPoint = CGPointApplyAffineTransform(CGPointMake(CGRectGetMaxX(rectangleBounds), CGRectGetMidY(rectangleBounds)), transform);
The code takes your initial rectangle (black rectangle) and angle (shown as black line), rotates the rectangle (you'll get blue rectangle), then finds bounding box of the rotated rectangle (red rectangle). Takes endpoints on the bounds (red dots) and transforms (rotates) them back using invert transform (blue dots).
Other solution might be finding intersection of your rectangle and a line defined by the angle.

How to Convert CAShapeLayer to CAGradientLayer?

my code of making CAShapeLayer is
UIBezierPath *circle = [UIBezierPath bezierPathWithArcCenter:CGPointMake(75, 125)
radius:50
startAngle:0
endAngle:1.8 * M_PI
clockwise:YES];
CAShapeLayer *circleLayer = [CAShapeLayer layer];
[circleLayer setFrame:CGRectMake(200 - 50, 300 - 50, 100, 100)];
circleLayer.path = circle.CGPath;
circleLayer.bounds = CGPathGetBoundingBox(circle.CGPath);
circleLayer.strokeColor = [UIColor colorWithRed:0.4 green:1.0 blue:0.2 alpha:0.5].CGColor;
[circleLayer setFillColor:[UIColor clearColor].CGColor];
circleLayer.lineWidth = 3.0;
if ([circleLayer animationForKey:#"SpinAnimation"] == nil) {
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat: 2 * M_PI];
animation.duration = 2.0f;
animation.repeatCount = INFINITY;
animation.removedOnCompletion = NO;
[circleLayer addAnimation:animation forKey:#"SpinAnimation"];
}
[self.view.layer addSublayer:circleLayer];
I want to make CAGradientLayer instead of CAShapeLayer.
REASON: I need to use gradient color in layer, which is not possible in CAShapeLayer.
I want to use yellow to black gradient upon Cirle.
Image :
In my required output, color is fetched from background image, and put some black at the end, or opacity 0 within layer.
Any Idea, suggestion.
Thanks.
One quick and dirty trick is adding a gradient layer as a sublayer then put some more layers with the background colors same as the superview (in the original question, it's white).
For example:
Add a gradient layer (and set proper cornerRadius).
Add a smaller layer with white background color at the center of the gradient layer (this will create a ring).
Create a small segment of arc with white background color.
However, this may not be suitable views with background images.
To solve this, you can create a custom subclass of UIView and override the drawRect method.
Suppose there are instance variables: startColor, endColor, radius, strokeWidth, and mirrored.
The variable mirrored is used to control the position of the gap (cut off left side or right side) and the rotation direction of the animation (clockwise or counter-clockwise).
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(ctx, kCGBlendModeNormal);
CGFloat gradientColors[4 * 2];
extractColorComponents(_startColor, gradientColors, 0);
extractColorComponents(_endColor, gradientColors, 4);
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, gradientColors, NULL, 2);
CGColorSpaceRelease(baseSpace);
baseSpace = nil;
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
gradient = nil;
// fill 'clear' color
CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextSetStrokeColorWithColor(ctx, [UIColor clearColor].CGColor);
CGContextSetBlendMode(ctx, kCGBlendModeClear);
CGFloat innerCircleRadius = _radius - _strokeWidth;
// fill an 'empty' hole inside the gradient part
CGContextFillEllipseInRect(ctx, (CGRect){
{_strokeWidth, _strokeWidth},
{innerCircleRadius * 2, innerCircleRadius * 2}
});
// fill an 'empty' segment of arc
CGFloat startAngle = _mirrored ? (0.9 * M_PI) : (-0.1 * M_PI);
CGFloat endAngle = startAngle + 0.2 * M_PI;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:(CGPoint){_radius, _radius}
radius:_radius - _strokeWidth * 0.5
startAngle:startAngle
endAngle:endAngle
clockwise:YES];
path.lineWidth = _strokeWidth;
CGContextAddPath(ctx, path.CGPath);
CGContextSetLineWidth(ctx, _strokeWidth + 2);
CGContextStrokePath(ctx);
CGContextSetBlendMode(ctx, kCGBlendModeNormal);
}
Here's the complete implementation of the view.
Final result:
Two rings:
You can add two rings in your viewDidLoad or somewhere else with the following code to achieve the desired effect:
- (void)viewDidLoad
{
[super viewDidLoad];
UIColor *startColor = [UIColor colorWithHue:0.02 saturation:0.74 brightness:0.91 alpha:1];
UIColor *endColor = [UIColor colorWithHue:0.57 saturation:0.76 brightness:0.86 alpha:1];
CGPoint center = CGPointMake(160, 200);
Spinner *outerSpinner = [[Spinner alloc] initWithCenter:center
radius:50
strokeWidth:3
startColor:startColor
endColor:endColor
mirrored:NO];
Spinner *innerSpinner = [[Spinner alloc] initWithCenter:center
radius:40
strokeWidth:3
startColor:startColor
endColor:endColor
mirrored:YES];
[self.view addSubview:outerSpinner];
[self.view addSubview:innerSpinner];
[outerSpinner startAnimating];
[innerSpinner startAnimating];
}
Result:
The mask property on CALayer uses the alpha component of the mask layer to determine what should be visible and not. Since both of your colors (black and white) are fully opaque (no transparency) the mask has no effect. To fix this you should change one of the colors to a clear color:
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor],
(id)[[UIColor clearColor] CGColor], nil];
EDIT
class Colors {
let colorTop = UIColor(red: 192.0/255.0, green: 38.0/255.0, blue: 42.0/255.0, alpha: 1.0).CGColor
let colorBottom = UIColor(red: 35.0/255.0, green: 2.0/255.0, blue: 2.0/255.0, alpha: 1.0).CGColor
let gl: CAGradientLayer
init() {
gl = CAGradientLayer()
gl.colors = [ colorTop, colorBottom]
gl.locations = [ 0.0, 1.0]
}
}

How to draw the gradient in iOS

I want to draw the Circular progress view with gradient as image :
Design Image
I tried the below code but the result color is not correct. This is my code:
//draw gradient
CGFloat colors [] = {
1.0, 0.7,
0.2, 0.4
};
CGFloat locations[] = { 0.0, 1.0 };
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceGray(); // gray colors want gray color space
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, locations, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;
CGContextSaveGState(context);
CGContextAddPath(context, progressPath);
CGContextClip(context);
CGRect boundingBox = CGPathGetBoundingBox(progressPath);
CGPoint gradientStart = CGPointMake(0.0, CGRectGetMinY(boundingBox));
CGPoint gradientEnd = CGPointMake(1.0, CGRectGetMaxY(boundingBox));
CGContextDrawLinearGradient(context, gradient, gradientStart, gradientEnd, 0);
CGGradientRelease(gradient), gradient = NULL;
CGContextRestoreGState(context);
The progressPath is the progress line with color :
[UIColor colorWithRed:21/255.0f green:182/255.0f blue:217/255.0f alpha:1.0]
UPDATE:
This is an image my code produced: Result Image
The result image is not the same the design, the color is not the same. I dont know why.
Please help me to correct this. Thanks in advance.
The kind of gradient you are looking for, where the color varies as a function of the angle around a center point, is called an angle gradient. Unfortunately, iOS does not have a built-in way to draw angle gradients, but you can do it using an open-source library such as paiv/AngleGradientLayer.
Working code:
- (void) setGradientWithFrame:(CGRect)frame
{
//create the gradient
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = frame;
//this example is white-black gradient, change it to the color you want..
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor whiteColor] CGColor], (id)[[UIColor blackColor] CGColor], nil];
gradient.cornerRadius = frame.size.height/2.0f;
[self.view.layer insertSublayer:gradient atIndex:0];
CALayer *whiteCircle = [CALayer layer];
whiteCircle.frame = CGRectMake(frame.origin.x + 10, frame.origin.y + 10, frame.size.width - 20, frame.size.height - 20);
whiteCircle.backgroundColor = [[UIColor whiteColor] CGColor];
whiteCircle.cornerRadius = whiteCircle.frame.size.width/2.0f;
[self.view.layer insertSublayer:whiteCircle atIndex:1];
CALayer *whitesquare = [CALayer layer];
whitesquare.frame = CGRectMake(50, frame.size.height - 20, 25, 25);
whitesquare.backgroundColor = [[UIColor whiteColor] CGColor];
whitesquare.transform = CATransform3DMakeRotation(-20.0 / 180.0 * M_PI, 0.0, 0.0, 1.0);
[self.view.layer insertSublayer:whitesquare atIndex:2];
}
to call the method:
[self setGradientWithFrame:CGRectMake(0, 0, 100, 100)];
PS. change the color to the color of gradient to the color you want:

How to fill a UIBezierPath with a gradient?

I've drawn a graph using a UIBezierPath. I can fill the area under the graph with a solid color but I want to fill the area under the graph with a gradient rather than with a solid color. But I'm not sure how to make the gradient apply only to the graph and not to the whole view, I've read a few questions but not found anything applicable.
This is the main graph drawing code:
// Draw the graph
UIBezierPath *barGraph = [UIBezierPath bezierPath];
barGraph.lineWidth = 1.0f;
[blueColor setStroke];
TCDataPoint *dataPoint = self.testData[0];
CGFloat x = [self convertTimeToXPoint:dataPoint.time];
CGFloat y = [self convertDataToYPoint:dataPoint.dataUsage];
CGPoint plotPoint = CGPointMake(x,y);
[barGraph moveToPoint:plotPoint];
for (int ii = 1; ii < [self.testData count]; ++ii)
{
dataPoint = self.testData[ii];
x = [self convertTimeToXPoint:dataPoint.time];
y = [self convertDataToYPoint:dataPoint.dataUsage];
plotPoint = CGPointMake(x, y);
[barGraph addLineToPoint:plotPoint];
}
[barGraph stroke];
I've been attempting to fill the graph by experimenting with code like the following, but to be honest am not %100 sure what I'm doing despite going through various tutorials and documentation:
[barGraph closePath];
CGFloat colors [] = {
1.0, 0.0, 0.0, 1.0,
1.0, 1.0, 1.0, 1.0
};
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
CGColorSpaceRelease(baseSpace);
CGPoint startPoint = CGPointMake(CGRectGetMidX(self.bounds), self.topY);
CGPoint endPoint = CGPointMake(CGRectGetMidX(self.bounds), self.bottomY);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
The easiest approach is to use the original graph bezier path to help you construct a mask and impose this mask on a gradient layer. Now impose the graph layer on top of the gradient layer.
So, for example, make a CAShapeLayer, close the graph bezier path, set its path as the shape layer's path, and fill it black. Now you have a mask that is the shape of the area under the graph. Now make a CAGradientLayer and make the CAShapeLayer its mask. In front of that, place the actual graph.
So for example (EDITED):
Here's the code I used to create that drawing (my bezier path is very simple, just four points joined by three lines, but you can see clearly that the area under it is a gradient):
CAShapeLayer* shape = [[CAShapeLayer alloc] init];
shape.frame = self.graph.bounds;
CGFloat h = shape.frame.size.height;
CGFloat w = shape.frame.size.width;
NSArray* points = #[
[NSValue valueWithCGPoint:CGPointMake(0,h-50)],
[NSValue valueWithCGPoint:CGPointMake(70,h-100)],
[NSValue valueWithCGPoint:CGPointMake(140,h-75)],
[NSValue valueWithCGPoint:CGPointMake(w,h-150)],
];
UIBezierPath* p = [UIBezierPath new];
[p moveToPoint:[points[0] CGPointValue]];
for (NSInteger i = 1; i < points.count; i++)
[p addLineToPoint:[points[i] CGPointValue]];
shape.path = p.CGPath;
shape.strokeColor = [UIColor blackColor].CGColor;
shape.lineWidth = 2;
shape.fillColor = nil;
CAGradientLayer* grad = [[CAGradientLayer alloc] init];
grad.frame = self.graph.bounds;
grad.colors = #[(id)[UIColor blueColor].CGColor,
(id)[UIColor yellowColor].CGColor];
CAShapeLayer* mask = [[CAShapeLayer alloc] init];
mask.frame = self.graph.bounds;
[p addLineToPoint:CGPointMake(w,h)];
[p addLineToPoint:CGPointMake(0,h)];
[p closePath];
mask.path = p.CGPath;
mask.fillColor = [UIColor blackColor].CGColor;
grad.mask = mask;
[self.graph.layer addSublayer:grad];
[self.graph.layer addSublayer:shape];

iOS Direction Glow

I'm trying to create a "mini-map." It's a circle filled with markers, and I want the border of the circle to glow in places to indicate to the user to indicate that more markers are beyond the minimap in a given direction.
I can give the circle a 'glowing' blue border by drawing, below it, a blue circle with a slightly larger radius. I think that I can make this blue border brighter in some places than others by giving its CALayer a mask. (I've tried giving it a gradient mask, and it works.)
Assuming that I can make the proper calculations to determine how bright a given pixel should be (given the position of markers beyond the minimap's viewport), how do I set the individual pixels of a CALayer? Or is there an easier way to accomplish what I'm looking for besides making a complicated alpha value calculation for each pixel in the circle?
Thanks.
Here's my solution. I drew a series of 1-pixel arcs, each with a different stroke color.
void AddGlowArc(CGContextRef context, CGFloat x, CGFloat y, CGFloat radius, CGFloat peakAngle, CGFloat sideAngle, CGColorRef colorRef){
CGFloat increment = .05;
for (CGFloat angle = peakAngle - sideAngle; angle < peakAngle + sideAngle; angle+=increment){
CGFloat alpha = (sideAngle - fabs(angle - peakAngle)) / sideAngle;
CGColorRef newColor = CGColorCreateCopyWithAlpha(colorRef, alpha);
CGContextSetStrokeColorWithColor(context, newColor);
CGContextAddArc(context, x, y, radius, angle, angle + increment, 0);
CGContextStrokePath(context);
}
}
And then, in DrawRect,
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
AddGlowArc(context, 160, 160, 160, angle, .2, [UIColor colorWithRed:0 green:.76 blue:.87 alpha:1].CGColor);
This was my [much longer] solution which would allow each glow-point layer to be added and animated individually if needed. The small circle is added on the circumference of a larger one and that larger circle is rotated. Enjoyed getting my head around this even though you answered the question
- (void)viewDidLoad
{
[super viewDidLoad];
CGPoint circleCenter = CGPointMake(300.f, 300.f);
CALayer *outerCircle = [self outerCircleToRotateWithCenter:circleCenter andRadius:100.f];
[self.view.layer addSublayer:outerCircle];
CGFloat rotationAngleInDeg = 270.f;
CGFloat rotationAngle = (M_PI * -rotationAngleInDeg)/180.f;
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, rotationAngle, 0.f, 0.f, -1.f);
[outerCircle setTransform:transform];
}
Using method:
- (CALayer *)outerCircleToRotateWithCenter:(CGPoint)circleCenter andRadius:(CGFloat )radius {
// outer circle
CAShapeLayer *container = [CAShapeLayer layer];
UIBezierPath *containerCircle = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(0.f, 0.f, radius, radius)];
[container setPath:containerCircle.CGPath];
[container setBounds:CGPathGetBoundingBox(containerCircle.CGPath)];
[container setPosition:circleCenter];
[container setAnchorPoint:CGPointMake(0.5f, 0.5f)];
[container setStrokeColor:[UIColor blackColor].CGColor]; // REMOVE
[container setFillColor:nil];
// smaller circle
CAShapeLayer *circle = [[CAShapeLayer alloc] init];
[container addSublayer:circle];
UIBezierPath *circlePath = [UIBezierPath
bezierPathWithOvalInRect:CGRectMake(0.f, 0.f, 25.f, 25.f)];
[circle setPath:circlePath.CGPath];
[circle setBounds:CGPathGetBoundingBox(circlePath.CGPath)];
[circle setFillColor:[UIColor orangeColor].CGColor];
[circle setAnchorPoint:CGPointMake(0.5f, 0.5f)];
[circle setPosition:CGPointMake(radius/2.f, 0.f)];
[circle setOpacity:0.4f];
// shadow
[circle setShadowOpacity:0.8f];
[circle setShadowRadius:4.f];
[circle setShadowOffset:CGSizeMake(0.f, 0.f)];
[circle setShadowColor:[UIColor orangeColor].CGColor];
[circle setShadowPath:circlePath.CGPath];
return container;
}
I'm guessing the smaller circle could be added and rotated in a single transform rather then being a sublayer.

Resources