Drawing Circular Gradient - ios

I would like to draw a circular gradient like in the example picture below. I can manage a radial gradient easily but I am not sure how to do a circular one.
I was thinking of drawing a gradient on a line and then transforming it to a circle. Would that be possible?
This is how I draw the radial gradient:
CGFloat a = MIN(self.frame.size.width, self.frame.size.height) - _lineWidth;
//Get current context
CGContextRef mainContext = UIGraphicsGetCurrentContext();
CGRect newRect = rect;
newRect.origin.x = ((self.frame.size.width - a)/2.0f);
newRect.origin.y = ((self.frame.size.height - a)/2.0f);
newRect.size.width = a;
newRect.size.height = a;
// Fill circle
const CGFloat *_components = CGColorGetComponents(_fillColor.CGColor);
CGFloat red = _components[0];
CGFloat green = _components[1];
CGFloat blue = _components[2];
CGFloat alpha = _components[3];
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGFloat redBallColors[] = {
red,green,blue,0.5,
red,green,blue,alpha,
};
CGFloat glossLocations[] = {0.0, 1.0};
CGGradientRef ballGradient = CGGradientCreateWithColorComponents(baseSpace, redBallColors, glossLocations, 2);
CGPoint startPoint = self.center;
CGPoint endPoint = self.center;
CGContextDrawRadialGradient(mainContext, ballGradient, startPoint, 0, endPoint, a/2, 0);
Thanks a lot!

Related

Black to White radial gradient is giving whole black screen

It should be very simple to create a black to white radial gradient using Core Graphics, but even after many tries I am not able to figure where I am going wrong.
I want something like this:
This is my code:
CGFloat radius = shapeRect.size.width/2.0;
CGPoint center = CGPointMake(shapeRect.origin.x+shapeRect.size.width/2, shapeRect.origin.y+shapeRect.size.height/2);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef context = CGBitmapContextCreate(NULL, shapeRect.size.width, shapeRect.size.height, 8, shapeRect.size.width*4, colorSpace, kCGImageAlphaNone);
CGFloat components[] = {0.0, 1.0};
CGFloat locations[] = {0.0, 1.0};
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 2);
CGContextDrawRadialGradient(context, gradient, center, 10, center, radius, 0);
CGImageRef img = CGBitmapContextCreateImage(context);
[maskedView setImage:[UIImage imageWithCGImage:img]];
CGGradientRelease(gradient);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
ShapeRect here has self.view bounds.
I am getting a whole black screen with no gradient as output.
I am using iPhone 7 simulator.
Your suggestions can save my day
Thank you.
Please check this snippet. You can change components and locations values to get what you want and to understand how the drawing works. Refer to CGGradientCreateWithColorComponentsdescription
#import "TestView.h"
#implementation TestView {
CGGradientRef _gradient;
}
-(void)drawRect:(CGRect)rect
{
CGFloat radius = self.frame.size.width / 2.0;
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGContextRef context = UIGraphicsGetCurrentContext();
CGGradientRef gradient = [self createGradient];
CGContextDrawRadialGradient(context, gradient, center, 10, center, radius, kCGGradientDrawsBeforeStartLocation);
}
-(CGGradientRef)createGradient
{
if (!_gradient) {
CGFloat components[] = { 0.0, 1.0, 0.5, 0.5, 1.0, 0.3 };
CGFloat locations[] = { 0.0, 0.6, 1.0 };
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
_gradient = CGGradientCreateWithColorComponents(colorSpace, components, locations, 3);
CGColorSpaceRelease(colorSpace);
}
return _gradient;
}
- (void)layoutSubviews
{
self.backgroundColor = [UIColor whiteColor];
self.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.layer.borderWidth = 1.;
}
#end
Result:

How to create a WaterDrop Shape with UIBezierPath

I write some code for creating the below with UIBezierPath but it didn't work. Can someone help me what's wrong with my code.
but i was tried with drawRect: it works fine....My problem is same i need in UIBezierPath as,
+ (UIBezierPath *)DropShape:(CGRect)Frame {}
my code using drawRect: is
CGContextRef context = UIGraphicsGetCurrentContext();
CGGradientRef gradient;
CGColorSpaceRef colorspace;
CGFloat locations[3] = { 0.0, 0.5, 1.0 };
NSArray *colors = #[(id)[UIColor redColor].CGColor];
colorspace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColors(colorspace,
(CFArrayRef)colors, locations);
CGPoint startPoint, endPoint;
CGFloat startRadius, endRadius;
startPoint.x = self.frame.size.width/2;
startPoint.y = 30;
endPoint.x = 175;
endPoint.y = 175;
startRadius = 0;
endRadius = 75;
CGContextDrawRadialGradient(context, gradient, startPoint,
startRadius, endPoint, endRadius, 0);
Thanks in advance...
There is an app called PaintCode which you can try for free.
You can draw onto a canvas and it will produce the drawRect code that you can copy and paste into your app.
I don't think you can copy from the trial version but it will create the path which you can type into your code manually.
It's worth a look.

Is there a way to animate colors of the CGContextDrawRadialGradient iOS

I use CGContextDrawRadialGradient function to make the gradient background.
I override drawRect: as follows:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
// Create gradient
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[] = {0.0, 0.8};
// Neutral colors
UIColor *centerColor = self.centerColor;
UIColor *edgeColor = self.edgeColor;
NSArray *colors = [NSArray arrayWithObjects:(__bridge id)centerColor.CGColor, (__bridge id)edgeColor.CGColor, nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
// Scaling transformation and keeping track of the inverse
CGAffineTransform scaleT = CGAffineTransformMakeScale(2, 1.0);
CGAffineTransform invScaleT = CGAffineTransformInvert(scaleT);
// Extract the Sx and Sy elements from the inverse matrix
// (See the Quartz documentation for the math behind the matrices)
CGPoint invS = CGPointMake(invScaleT.a, invScaleT.d);
// Transform center and radius of gradient with the inverse
//CGPoint center = CGPointMake((self.bounds.size.width / 2) * invS.x, (self.bounds.size.height / 2) * invS.y);
CGPoint center = CGPointMake(-20, (self.bounds.size.height / 2) * invS.y);
//CGFloat radius = (self.bounds.size.width / 2) * invS.x;
CGFloat radius = self.bounds.size.width; // MEEEEE
// Draw the gradient with the scale transform on the context
CGContextScaleCTM(ctx, scaleT.a, scaleT.d);
CGContextDrawRadialGradient(ctx, gradient, center, 0, center, radius, kCGGradientDrawsBeforeStartLocation);
// Reset the context
CGContextScaleCTM(ctx, invS.x, invS.y);
// Continue to draw whatever else ...
// Clean up the memory used by Quartz
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
And allow to change colors of the background from time to time via :
- (void) setGradientCenterColor:(UIColor *)centerColor andEdgeColor:(UIColor *)edgeColor {
self.centerColor = centerColor;
self.edgeColor = edgeColor;
[self setNeedsDisplay];
}
Is there a way to animate the color properties? Or I need to do it manually ?
Thank you very much for answer in advance :)
Make the global iVars NSArray *centerColorArray NSArray *edgeColorArray and NSInteger index.
Then, in viewDidLoad
index = 0;
centerColorArray = #[#"color1", #"color2"];
edgeColorArray = #[#"color1", #"color2"];
Use NSTimer for animation behavior
-(void)animate
{
[self changeColor:index];
[YOUR_INSTANCE setGradientCenterColor:centerColor andEdgeColor:edgeColor];
index++;
}
-(void)changeColor:(int)index
{
if(index == [centerColorArray count] - 1)
index = 0;
centerColor = centerColorArray[index];
edgeColor = edgeColorArray[index];
}
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(animate) userInfo:nil repeats:YES];

Drawing a pattern along a path

My goal is to take a pattern like this
and draw it repeatedly along a circular path to produce something similar to this image:
I found several code examples in other questions and an full demo project here but the result is this:
I think the difference between the two images is obvious, but I find it hard to describe (pardon my lack of graphics vocabulary). The result seems to be tiling without the desired rotation/deformation of the pattern. I think I can live with the lack of deformation, but the rotation is key. I think that perhaps the draw callback could/should be modified to include a rotation, but can't figure out how to retrieve/determine the angle at the point of the callback.
I considered an approach where I manually deformed/rotated the image and drew it several times around a centerpoint to achieve the effect I want, but I believe that CoreGraphics could do it with more efficiency and with less code.
Any suggestions about how to achieve the result I want would be appreciated.
Here is the relevant code from the ChalkCircle project:
const float kPatternWidth = 8;
const float kPatternHeight = 8;
void DrawPatternCellCallback(void *info, CGContextRef cgContext)
{
UIImage *patternImage = [UIImage imageNamed:#"chalk_brush.png"];
CGContextDrawImage(cgContext, CGRectMake(0, 0, kPatternWidth, kPatternHeight), patternImage.CGImage);
}
- (void)drawRect:(CGRect)rect {
float startDeg = 0; // where to start drawing
float endDeg = 360; // where to stop drawing
int x = self.center.x;
int y = self.center.y;
int radius = (self.bounds.size.width > self.bounds.size.height ? self.bounds.size.height : self.bounds.size.width) / 2 * 0.8;
CGContextRef ctx = UIGraphicsGetCurrentContext();
const CGRect patternBounds = CGRectMake(0, 0, kPatternWidth, kPatternHeight);
const CGPatternCallbacks kPatternCallbacks = {0, DrawPatternCellCallback, NULL};
CGAffineTransform patternTransform = CGAffineTransformIdentity;
CGPatternRef strokePattern = CGPatternCreate(
NULL,
patternBounds,
patternTransform,
kPatternWidth, // horizontal spacing
kPatternHeight,// vertical spacing
kCGPatternTilingNoDistortion,
true,
&kPatternCallbacks);
CGFloat color1[] = {1.0, 1.0, 1.0, 1.0};
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetStrokeColorSpace(ctx, patternSpace);
CGContextSetStrokePattern(ctx, strokePattern, color1);
CGContextSetLineWidth(ctx, 4.0);
CGContextMoveToPoint(ctx, x, y - radius);
CGContextAddArc(ctx, x, y, radius, (startDeg-90)*M_PI/180.0, (endDeg-90)*M_PI/180.0, 0);
CGContextClosePath(ctx);
CGContextDrawPath(ctx, kCGPathStroke);
CGPatternRelease(strokePattern);
strokePattern = NULL;
CGColorSpaceRelease(patternSpace);
patternSpace = NULL;
}
.SOLUTION FROM SAM
I modified sam's solution to handle non-square patterns, center the result, and remove hard coded numbers by calculating them from the passed in image:
#define MAX_CIRCLE_DIAMETER 290.0f
#define OVERLAP 1.5f
-(void) drawInCircle:(UIImage *)patternImage
{
int numberOfImages = 12;
float diameter = (MAX_CIRCLE_DIAMETER * numberOfImages * patternImage.size.width) / ( (2.0 * M_PI * patternImage.size.height) + (numberOfImages * patternImage.size.width));
//get the radius, circumference and image size
CGRect replicatorFrame = CGRectMake((320-diameter)/2.0f, 60.0f, diameter, diameter);
float radius = diameter/2;
float circumference = M_PI * diameter;
float imageWidth = circumference/numberOfImages;
float imageHeight = imageWidth * patternImage.size.height / patternImage.size.width;
//create a replicator layer and add it to our view
CAReplicatorLayer *replicator = [CAReplicatorLayer layer];
replicator.frame = replicatorFrame;
[self.view.layer addSublayer:replicator];
//configure the replicator
replicator.instanceCount = numberOfImages;
//apply a rotation transform for each instance
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, M_PI / (numberOfImages/2), 0, 0, 1);
replicator.instanceTransform = transform;
//create a sublayer and place it inside the replicator
CALayer *layer = [CALayer layer];
//the frame places the layer in the middle of the replicator layer and on the outside of
//the replicator layer so that the the size is accurate relative to the circumference
layer.frame = CGRectMake(radius - (imageWidth/2.0) - (OVERLAP/2.0), -imageHeight/2.0, imageWidth+OVERLAP, imageHeight);
layer.anchorPoint = CGPointMake(0.5, 1);
[replicator addSublayer:layer];
//apply a perspective transform to the layer
CATransform3D perspectiveTransform = CATransform3DIdentity;
perspectiveTransform.m34 = 1.0f / -radius;
perspectiveTransform = CATransform3DRotate(perspectiveTransform, (M_PI_4), -1, 0, 0);
layer.transform = perspectiveTransform;
//set the image as the layer's contents
layer.contents = (__bridge id)patternImage.CGImage;
}
Using Core Animation's replicator layer, I managed to create this result:
I think it's close to what your looking for. In this example all the images are square with a 3d X rotation applied to each of them.
#import <QuartzCore/QuartzCore.h>
//set the number of images and the diameter (width) of the circle
int numberOfImages = 30;
float diameter = 450.0f;
//get the radius, circumference and image size
float radius = diameter/2;
float circumference = M_PI * diameter;
float imageSize = circumference/numberOfImages;
//create a replicator layer and add it to our view
CAReplicatorLayer *replicator = [CAReplicatorLayer layer];
replicator.frame = CGRectMake(100.0f, 100.0f, diameter, diameter);
[self.view.layer addSublayer:replicator];
//configure the replicator
replicator.instanceCount = numberOfImages;
//apply a rotation transform for each instance
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, M_PI / (numberOfImages/2), 0, 0, 1);
replicator.instanceTransform = transform;
//create a sublayer and place it inside the replicator
CALayer *layer = [CALayer layer];
//the frame places the layer in the middle of the replicator layer and on the outside of the replicator layer so that the the size is accurate relative to the circumference
layer.frame = CGRectMake(radius - (imageSize/2), -imageSize/2, imageSize, imageSize);
layer.anchorPoint = CGPointMake(0.5, 1);
[replicator addSublayer:layer];
//apply a perspective transofrm to the layer
CATransform3D perspectiveTransform = CATransform3DIdentity;
perspectiveTransform.m34 = 1.0f / -radius;
perspectiveTransform = CATransform3DRotate(perspectiveTransform, (M_PI_4), -1, 0, 0);
layer.transform = perspectiveTransform;
//set the image as the layer's contents
layer.contents = (__bridge id)[UIImage imageNamed:#"WCR3Q"].CGImage;

set line width to radial gradient

I am using radial gradient to draw circle. It is working fine but now I want to set border color and border width to that. I tried it but it was not working.
Here is my code,
CGGradientRef gradient;
CGColorSpaceRef colorSpace;
CGFloat locations[] = {0.0,1.0};
CGFloat components[] = { red2,green2,blue2,1.0,red1,green1,blue1,1.0 };
colorSpace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColorComponents(colorSpace,components,locations,
sizeof(locations)/sizeof(CGFloat));
CGPoint start = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2 ), end = CGPointMake(self.bounds.size.width/2 , self.bounds.size.height/2);
CGFloat startRadius = 0.0, endRadius = radius;
CGContextDrawRadialGradient(contextRef,gradient,start,startRadius,end,endRadius,0);
CGContextSetLineWidth(contextRef, 5.0);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
How can I set border color and width to it?
First add QuartzCore Framework and then try this code...
[[Firstbtn layer]setBorderColor:[[UIColor blackColor]CGColor]];
[[self.Firstbtn layer]setBorderWidth:2.3];
[[self.Firstbtn layer]setCornerRadius:15];

Resources