Drawing a Gradient Color in an Arc with a Rounded Edge

Drawing a gradient color in an arc with a rounded edge

override func draw(_ rect: CGRect) {

//Add gradient layer
let gl = CAGradientLayer()
gl.frame = rect
gl.colors = [UIColor.red.cgColor, UIColor.blue.cgColor]
layer.addSublayer(gl)

//create mask in the shape of arc
let sl = CAShapeLayer()
sl.frame = rect
sl.lineWidth = 15.0
sl.strokeColor = UIColor.red.cgColor
let path = UIBezierPath()
path.addArc(withCenter: .zero, radius: 250.0, startAngle: 10.0.radians, endAngle: 60.0.radians, clockwise: true)
sl.fillColor = UIColor.clear.cgColor
sl.lineCap = kCALineCapRound
sl.path = path.cgPath

//Add mask to gradient layer
gl.mask = sl

}

An arc drawn with gradient

Hope this helps!!

How to draw Arc with curved edge in iOS?

The easiest approach is probably:

  1. Create a single arc path (along the middle of your target arc) as a path
  2. Set the line width (20 in your case) and the line cap (rounded caps)
  3. Convert the path into a stroked path (CGContextReplacePathWithStrokedPath)
  4. Continue with your existing code for the gradient

The stroking will convert your 90 degree arc path, which is infinitely narrow, into an outline of a line that is 20 pixel wide and has rounded line endings.

The code could approximately look like this:

- (UIBezierPath *)arcWithRoundedCornerAt:(CGPoint)center
startAngle:(CGFloat)startAngle
endAngle:(CGFloat)endAngle
innerRadius:(CGFloat)innerRadius
outerRadius:(CGFloat)outerRadius
cornerRadius:(CGFloat)cornerRadius
context:(CGContext)context
{

CGFloat radius = (innerRadius + outerRadius) / 2.0;

CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, true);

CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, outerRadius - innerRadius);

CGContextReplacePathWithStrokedPath(context)

return [UIBezierPath bezierPathWithCGPath: CGContextCopyPath(context)];
}

How to draw a gradient arc with Core Graphics/iPhone?

I spent a long time searching for how to do this, too, so I thought I'd post the way I ended up doing it. It turns out both answers are in the excellent answer to this question:

Draw segments from a circle or donut

For my purposes, I only used the drawing and gradient parts of that answer. The structure looks more or less like this...

CGContextRef context = UIGraphicsGetCurrentcontext();

CGFloat arcStartAngle = M_PI;
CGFloat arcEndAngle = 2 * M_PI;

CGPoint startPoint = CGPointMake(...);
CGPoint endPoint = CGPointMake(...);

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

CGFloat colors[] =
{
1.0, 0.0, 0.0, 1.0, //RGBA values (so red to green in this case)
0.0, 1.0, 0.0, 1.0
};

CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, NULL, 2);
//Where the 2 is for the number of color components. You can have more colors throughout //your gradient by adding to the colors[] array, and changing the components value.

CGColorSpaceRelease(colorSpace);

//Now for the arc part...

CGMutablePathRef arc = CGPathCreateMutable();

CGPathMoveToPoint(arc, NULL, startPoint.x, startPoint.y);

//Here, the CGPoint self.arcCenter is the point around which the arc is placed, so maybe the
//middle of your view. self.radius is the distance between this center point and the arc.
CGPathAddArc(arc, NULL, self.arcCenter.x, self.arcCenter.y, self.radius,
arcStartAngle, arcEndAngle, YES);

//This essentially draws along the path in an arc shape
CGPathRef strokedArc = CGPathCreateCopyByStrokingPath(arc, NULL, 5.0f,
kCGLineCapButt, kCGLineJoinMiter, 10);

CGContextSaveGState(context);

CGContextAddPath(context, strokedArc);
CGContextClip(context);

CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);

CGContextDrawPath(context, kCGPathFillStroke);

CGGradientRelease(gradient);
CGContextRestoreGState(context);

//This all draws a gradient that is much larger than the arc itself, but using
//CGContextClip, it clips out everything EXCEPT the colors in the arc. Saving and Restoring
//the state allows you to preserve any other drawing going on. If you didn't use these,
//then all other drawing would also be clipped.

I hope this helps. If any of this is unclear, I recommend you check out the question link above. The answer to that questions contains everything I used in this answer and a few more cool and useful drawing tips.

Draw arc with linear gradient in HTML5 canvas

Yes, it's possible! There is a method in Javascript, named createLinearGradient which gets as source the canvas context and applies a gradient defined by sx, sy, dx, dy coordinates. The first two options defines the starting coordinates and last two the ending coordinates.

var gradient = context.createLinearGradient(sx, sy, dx, dy);

After invoking this method you can apply gradient colors to your canvas by calling the colorStop method:

gradient.addColorStop(0,   '#f00'); // red
gradient.addColorStop(0.5, '#ff0'); // yellow
gradient.addColorStop(1, '#00f'); // blue

These are the basic ingredients for implementing a gradient on a canvas. The next step would be to calculate the circular color gradient positions if you need a circular gradient. This can be satisfied by the following formula:

var applyAngle = function (point, angle, distance) {
return {
x : point.x + (Math.cos(angle) * distance),
y : point.y + (Math.sin(angle) * distance)
};
};

Then plugin the resulted x and y position into the createLinearGradient method, which would create a nice looking circular gradient. Of course you need to use the arc method to make it circular.

How to draw a linear gradient arc with Qt QPainter?

I know this is an old question but I came across it some days ago and I think I have a solution. What you want is to create a conical gradient and clip the disk you want to use as circular loading bar. Here is an example:

widget.h:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class QPaintEvent;

class Widget : public QWidget
{
Q_OBJECT

public:
explicit Widget(QWidget *parent = 0);
~Widget();

void setLoadingAngle(int loadingAngle);
int loadingAngle() const;

void setDiscWidth(int width);
int discWidth() const;

protected:
void paintEvent(QPaintEvent *);

private:
int m_loadingAngle;
int m_width;
};

#endif // WIDGET_H

widget.cpp:

#include "widget.h"

#include <QPaintEvent>
#include <QPainter>
#include <QConicalGradient>
#include <QPen>

Widget::Widget(QWidget *parent) :
QWidget(parent),
m_loadingAngle(0),
m_width(0)
{
}

Widget::~Widget()
{
}

void Widget::setLoadingAngle(int loadingAngle)
{
m_loadingAngle = loadingAngle;
}

int Widget::loadingAngle() const
{
return m_loadingAngle;
}

void Widget::setDiscWidth(int width)
{
m_width = width;
}

int Widget::discWidth() const
{
return m_width;
}

void Widget::paintEvent(QPaintEvent *)
{
QRect drawingRect;
drawingRect.setX(rect().x() + m_width);
drawingRect.setY(rect().y() + m_width);
drawingRect.setWidth(rect().width() - m_width * 2);
drawingRect.setHeight(rect().height() - m_width * 2);

QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);

QConicalGradient gradient;
gradient.setCenter(drawingRect.center());
gradient.setAngle(90);
gradient.setColorAt(0, QColor(178, 255, 246));
gradient.setColorAt(1, QColor(5, 44, 50));

int arcLengthApproximation = m_width + m_width / 3;
QPen pen(QBrush(gradient), m_width);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
painter.drawArc(drawingRect, 90 * 16 - arcLengthApproximation, -m_loadingAngle * 16);
}

main.cpp:

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);

Widget w;
w.setDiscWidth(20);
w.setLoadingAngle(270);
w.show();

return a.exec();
}

And the result is:

Sample Image

Of course, it is not the complete and exact solution but I think it is everything you need to know in order to achieve what you want. The rest are details not hard to implement.

Apply gradient color to arc created with UIBezierPath

You can use a CAGradientLayer to get the gradient effect, and use the CAShapeLayer as a mask.

e.g.:

- (void)viewDidLoad
{
[super viewDidLoad];

int radius = 100;

CAShapeLayer *arc = [CAShapeLayer layer];
arc.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(100, 50) radius:radius startAngle:60.0 endAngle:0.0 clockwise:YES].CGPath;

arc.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);

arc.fillColor = [UIColor clearColor].CGColor;
arc.strokeColor = [UIColor purpleColor].CGColor;
arc.lineWidth = 15;
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
drawAnimation.duration = 5.0; // "animate over 10 seconds or so.."
drawAnimation.repeatCount = 1.0; // Animate only once..
drawAnimation.removedOnCompletion = NO; // Remain stroked after the animation..
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:10.0f];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
[arc addAnimation:drawAnimation forKey:@"drawCircleAnimation"];

CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = self.view.frame;
gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor,(__bridge id)[UIColor blueColor].CGColor ];
gradientLayer.startPoint = CGPointMake(0,0.5);
gradientLayer.endPoint = CGPointMake(1,0.5);

[self.view.layer addSublayer:gradientLayer];
//Using arc as a mask instead of adding it as a sublayer.
//[self.view.layer addSublayer:arc];
gradientLayer.mask = arc;

}

Draw rounded linear gradient (or extended radial gradient) with CoreGraphics

I don't think there is an API for that, but you can get the same effect if you first draw a radial gradient, say, in an (N+1)x(N+1) size bitmap context, then convert the image from the context to a resizable image with left and right caps set to N.

Pseudocode:

UIGraphicsBeginImageContextWithOptions(CGSizeMake(N+1,N+1), NO, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();

// <draw the gradient into 'context'>

UIImage* gradientBase = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

UIImage* gradientImage = [gradientBase resizableImageWithCapInsets:UIEdgeInsetsMake(0,N,0,N)];

In case you want the image to scale vertically as well, you just have to set the caps to UIEdgeInsetsMake(N,N,N,N).

Make a rotating gradient on a circle?

This was actually not that trivial. Perhaps there was an easier way to do this and I just took the hard approach. Or perhaps this is something newish.

What I did was I ended up drawing a gradient box inside of the circle. On an interval the circle changes its start and end points so that the gap rotates. While that happens, I recalculate the gradient box to fit into the new gap and then draw that on there. The result is a nice effect.

In this demo, I have set it up to cycle randomly over colors and different sizes in order to show what different spinners look like and to have some fun.

jsFiddle Demo

The meat of the operation is in this function:

function showSpinner(
startAngle,endAngle,direction,radius,line,context,color,shadow)
{
context.beginPath();
var a = startAngle / Math.PI;
a = a % 2;
a = 2 - a;
a *= Math.PI;
var x = radius + Math.cos(a)*radius*1.7;
var y = radius - Math.sin(a)*radius*1.7;
var gradient = context.createLinearGradient(radius,radius,x,y);
gradient.addColorStop(0.05, color);
gradient.addColorStop(.60, '#FFFFFF');
context.lineWidth = line;
context.lineCap = "round";
context.shadowBlur = 10;
if( shadow === true )context.shadowColor = '#727272';
context.arc(radius, radius, radius-line, startAngle, endAngle, direction);
context.strokeStyle = gradient;
context.stroke();
}

This function leverages the above drawing function in order to provide animation

function spinner(obj){
var radius,line,color,shadow;
if( obj && obj.hasOwnProperty("shadow") ){
shadow = true;
}else{ radius = 75; }
if( obj && obj.hasOwnProperty("radius") ){
radius = obj.radius;
}else{ radius = 75; }
if( obj && obj.hasOwnProperty("line") ){
line = obj.line;
}else{ line = 7; }
var speed = {inc:0.04,loop:15};
if( obj && obj.hasOwnProperty("speed") ){
if( obj.speed == "slow" ){
speed = {inc:0.02,loop:25};
}
}
if( obj && obj.hasOwnProperty("color") ){
color = obj.color;
}else{ color = '#3b749a'; }
var canvas = document.getElementById('myCanvas');
canvas.style.height = 2*(radius+line) + "px";
canvas.style.width = 4*(radius+line) + "px";
var context = canvas.getContext('2d');
var startAngle,endAngle;
var counterClockwise = false;
var sa = 1.2;
var ea = 0.85;

var spinner = setInterval(function(){
canvas.width = canvas.width;
sa += speed.inc;
ea += speed.inc;
startAngle = sa * Math.PI;
endAngle = ea * Math.PI;
showSpinner(
startAngle,
endAngle,
counterClockwise,
radius,
line,
context,
color,
shadow
);
},speed.loop);
setTimeout(function(){ clearInterval(spinner);},15000);
return spinner;
}

But all you really have to do to use this, is

spinner()

Pass in an argument if you would like. Your options are radius, line, color, shadow, and speed.

var obj = {};
obj.line = int - the size in pixels of the line width
obj.radius = int - the radius of the circle drawn (currently maxed at 75 for viewing, but you can always change the size of the canvas if you want)
obj.color = string - the color that the line of the spinner will be
obj.shadow = bool - show the shadow or don't show it
obj.speed = string - Only supports "slow" right now. Otherwise it will be fast


Related Topics



Leave a reply



Submit