如何使用Core Graphics / iPhone绘制渐变线(淡入/淡出)?

时间:2023-01-22 20:55:10

I know how to draw a simple line:

我知道如何绘制一条简单的线条:

CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextMoveToPoint(context, x, y);
CGContextAddLineToPoint(context, x2, y2);
CGContextStrokePath(context);

And I know how to do a gradient rectangle, i.g.:

而且我知道如何做一个渐变矩形,例如:

CGColorSpaceRef myColorspace=CGColorSpaceCreateDeviceRGB();
size_t num_locations = 2;
CGFloat locations[2] = { 1.0, 0.0 };
CGFloat components[8] = { 0.0, 0.0, 0.0, 1.0,    1.0, 1.0, 1.0, 1.0 };

CGGradientRef myGradient = CGGradientCreateWithColorComponents(myColorspace, components, locations, num_locations);

CGPoint myStartPoint, myEndPoint;
myStartPoint.x = 0.0;
myStartPoint.y = 0.0;
myEndPoint.x = 0.0;
myEndPoint.y = 10.0;
CGContextDrawLinearGradient (context, myGradient, myStartPoint, myEndPoint, 0);

But how could I draw a line with a gradient, i.g. fading in from black to white (and maybe fading out to black on the other side as well) ?

但我怎么能用渐变画一条线,例如。从黑色渐变到白色(也可能在另一边淡出黑色)?

5 个解决方案

#1


27  

It is possible to stroke arbitrary paths with a gradient, or any other fill effect, such as a pattern.

可以使用渐变或任何其他填充效果(例如图案)来描边任意路径。

As you have found, stroked paths are not rendered with the current gradient. Only filled paths use the gradient (when you turn them in to a clip and then draw the gradient).

如您所见,描边路径不会使用当前渐变进行渲染。只有填充的路径才会使用渐变(当您将它们转换为剪辑然后绘制渐变时)。

However, Core Graphics has an amazingly cool procedure CGContextReplacePathWithStrokedPath that will transform the path you intend to stroke in to a path that is equivalent when filled.

但是,Core Graphics有一个非常酷的过程CGContextReplacePathWithStrokedPath,它将您想要描边的路径转换为填充时等效的路径。

Behind the scenes, CGContextReplacePathWithStrokedPath builds up an edge polygon around your stroke path and switches that for the path you have defined. I'd speculate that the Core Graphics rendering engine probably does this anyway in calls to CGContextStrokePath.

在幕后,CGContextReplacePathWithStrokedPath围绕笔划路径构建边缘多边形,并为您定义的路径切换。我推测Core Graphics渲染引擎在调用CGContextStrokePath时可能会这样做。

Here's Apple's documentation on this:

这是Apple关于此的文档:

Quartz creates a stroked path using the parameters of the current graphics context. The new path is created so that filling it draws the same pixels as stroking the original path. You can use this path in the same way you use the path of any context. For example, you can clip to the stroked version of a path by calling this function followed by a call to the function CGContextClip.

Quartz使用当前图形上下文的参数创建描边路径。创建新路径,以便填充它绘制与描绘原始路径相同的像素。您可以像使用任何上下文的路径一样使用此路径。例如,您可以通过调用此函数然后调用函数CGContextClip来剪切到路径的描边版本。

So, convert your path in to something you can fill, turn that in to a clip, and then draw your gradient. The effect will be as if you had stroked the path with the gradient.

因此,将您的路径转换为可以填充的内容,将其转换为剪辑,然后绘制渐变。效果就像你用渐变描绘了路径一样。

Code

It'll look something like this…

它看起来像这样......

    // Get the current graphics context.
    //
    const CGContextRef context = UIGraphicsGetCurrentContext();

    // Define your stroked path. 
    //
    // You can set up **anything** you like here.
    //
    CGContextAddRect(context, yourRectToStrokeWithAGradient);

    // Set up any stroking parameters like line.
    //
    // I'm setting width. You could also set up a dashed stroke
    // pattern, or whatever you like.
    //
    CGContextSetLineWidth(context, 1);

    // Use the magical call.
    //
    // It turns your _stroked_ path in to a **fillable** one.
    //
    CGContextReplacePathWithStrokedPath(context);

    // Use the current _fillable_ path in to define a clipping region.
    //
    CGContextClip(context);

    // Draw the gradient.
    //
    // The gradient will be clipped to your original path.
    // You could use other fill effects like patterns here.
    //
    CGContextDrawLinearGradient(
      context, 
      yourGradient, 
      gradientTop, 
      gradientBottom, 
      0
    );

Further notes

It's worth emphasising part of the documentation above:

值得强调的是上面的部分文档:

Quartz creates a stroked path using the parameters of the current graphics context.

Quartz使用当前图形上下文的参数创建描边路径。

The obvious parameter is the line width. However, all line drawing state is used, such as stroke pattern, mitre limit, line joins, caps, dash patterns, etc. This makes the approach extremely powerful.

明显的参数是线宽。但是,使用所有线条绘制状态,例如笔划图案,斜接限制,线条连接,大写字母,虚线图案等。这使得该方法非常强大。

For additional details see this answer of this S.O. question.

有关其他详细信息,请参阅此S.O.的答案。题。

#2


20  

After several tries I'm now sure that gradients doesn't affect strokes, so I think it's impossible to draw gradient lines with CGContextStrokePath(). For horizontal and vertical lines the solution is to use CGContextAddRect() instead, which fortunately is what I need. I replaced

经过几次尝试,我现在确定渐变不会影响笔画,所以我认为用CGContextStrokePath()绘制渐变线是不可能的。对于水平和垂直线,解决方案是使用CGContextAddRect(),幸运的是我需要的。我换了

CGContextMoveToPoint(context, x, y);
CGContextAddLineToPoint(context, x2, y2);
CGContextStrokePath(context);

with

CGContextSaveGState(context);
CGContextAddRect(context, CGRectMake(x, y, width, height));
CGContextClip(context);
CGContextDrawLinearGradient (context, gradient, startPoint, endPoint, 0);
CGContextRestoreGState(context);

and everything works fine. Thanks to Brad Larson for the crucial hint.

一切正常。感谢Brad Larson的关键暗示。

#3


8  

After you draw the line, you can call

画线后,你可以打电话

CGContextClip(context);

to clip further drawing to your line area. If you draw the gradient, it should now be contained within the line area. Note that you will need to use a clear color for your line if you just want the gradient to show, and not the line underneath it.

将进一步的绘图剪辑到您的线区域。如果绘制渐变,现在应该包含在线条区域内。请注意,如果您只想显示渐变而不是下面的线,则需要为线条使用清晰的颜色。

There is the possibility that a line will be too thin for your gradient to show up, in which case you can use CGContextAddRect() to define a thicker area.

线条可能太薄而无法显示渐变,在这种情况下,您可以使用CGContextAddRect()来定义更粗的区域。

I present a more elaborate example of using this context clipping in my answer here.

我在这里的答案中提供了一个更详细的使用此上下文剪辑的示例。

#4


8  

You can use Core Animation layers. You can use a CAShaperLayer for your line by settings its path property and then you can use a CAGradientLayer as a layer mask to your shape layer that will cause the line to fade.

您可以使用Core Animation图层。您可以通过设置其路径属性为您的线路使用CAShaperLayer,然后您可以使用CAGradientLayer作为形状图层的图层蒙版,这将导致线条淡化。

Replace your CGContext... calls with calls to CGPath... calls to create the line path. Set the path field on the layer using that path. Then in your gradient layer, specify the colors you want to use (probably black to white) and then set the mask to be the line layer like this:

用调用CGPath ...调用替换你的CGContext ...调用来创建行路径。使用该路径在图层上设置路径字段。然后在渐变图层中,指定要使用的颜色(可能是黑色到白色),然后将蒙版设置为线条图层,如下所示:

 [gradientLayer setMask:lineLayer];

What's cool about the gradient layer is that is allows you to specify a list of locations where the gradient will stop, so you can fade in and fade out. It only supports linear gradients, but it sounds like that may fit your needs.

渐变层有什么好处,它允许您指定渐变停止的位置列表,这样您就可以淡入和淡出。它只支持线性渐变,但听起来可能符合您的需求。

Let me know if you need clarification.

如果您需要澄清,请告诉我。

EDIT: Now that I think of it, just create a single CAGradientLayer that is the width/height of the line you desire. Specify the gradient colors (black to white or black to clear color) and the startPoint and endtPoints and it should give you what you need.

编辑:现在我想起来,只需创建一个CAGradientLayer,它是你想要的线的宽度/高度。指定渐变颜色(黑色到白色或黑色以清除颜色)以及startPoint和endtPoints,它应该为您提供所需的颜色。

#5


0  

CGContextMoveToPoint(context, frame.size.width-200, frame.origin.y+10);
CGContextAddLineToPoint(context, frame.size.width-200, 100-10);
CGFloat colors[16] = { 0,0, 0, 0,
    0, 0, 0, .8,
    0, 0, 0, .8,
    0, 0,0 ,0 };
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 4);

CGContextSaveGState(context);
CGContextAddRect(context, CGRectMake(frame.size.width-200,10, 1, 80));
CGContextClip(context);
CGContextDrawLinearGradient (context, gradient, CGPointMake(frame.size.width-200, 10), CGPointMake(frame.size.width-200,80), 0);
CGContextRestoreGState(context);

its work for me.

它为我工作。

#1


27  

It is possible to stroke arbitrary paths with a gradient, or any other fill effect, such as a pattern.

可以使用渐变或任何其他填充效果(例如图案)来描边任意路径。

As you have found, stroked paths are not rendered with the current gradient. Only filled paths use the gradient (when you turn them in to a clip and then draw the gradient).

如您所见,描边路径不会使用当前渐变进行渲染。只有填充的路径才会使用渐变(当您将它们转换为剪辑然后绘制渐变时)。

However, Core Graphics has an amazingly cool procedure CGContextReplacePathWithStrokedPath that will transform the path you intend to stroke in to a path that is equivalent when filled.

但是,Core Graphics有一个非常酷的过程CGContextReplacePathWithStrokedPath,它将您想要描边的路径转换为填充时等效的路径。

Behind the scenes, CGContextReplacePathWithStrokedPath builds up an edge polygon around your stroke path and switches that for the path you have defined. I'd speculate that the Core Graphics rendering engine probably does this anyway in calls to CGContextStrokePath.

在幕后,CGContextReplacePathWithStrokedPath围绕笔划路径构建边缘多边形,并为您定义的路径切换。我推测Core Graphics渲染引擎在调用CGContextStrokePath时可能会这样做。

Here's Apple's documentation on this:

这是Apple关于此的文档:

Quartz creates a stroked path using the parameters of the current graphics context. The new path is created so that filling it draws the same pixels as stroking the original path. You can use this path in the same way you use the path of any context. For example, you can clip to the stroked version of a path by calling this function followed by a call to the function CGContextClip.

Quartz使用当前图形上下文的参数创建描边路径。创建新路径,以便填充它绘制与描绘原始路径相同的像素。您可以像使用任何上下文的路径一样使用此路径。例如,您可以通过调用此函数然后调用函数CGContextClip来剪切到路径的描边版本。

So, convert your path in to something you can fill, turn that in to a clip, and then draw your gradient. The effect will be as if you had stroked the path with the gradient.

因此,将您的路径转换为可以填充的内容,将其转换为剪辑,然后绘制渐变。效果就像你用渐变描绘了路径一样。

Code

It'll look something like this…

它看起来像这样......

    // Get the current graphics context.
    //
    const CGContextRef context = UIGraphicsGetCurrentContext();

    // Define your stroked path. 
    //
    // You can set up **anything** you like here.
    //
    CGContextAddRect(context, yourRectToStrokeWithAGradient);

    // Set up any stroking parameters like line.
    //
    // I'm setting width. You could also set up a dashed stroke
    // pattern, or whatever you like.
    //
    CGContextSetLineWidth(context, 1);

    // Use the magical call.
    //
    // It turns your _stroked_ path in to a **fillable** one.
    //
    CGContextReplacePathWithStrokedPath(context);

    // Use the current _fillable_ path in to define a clipping region.
    //
    CGContextClip(context);

    // Draw the gradient.
    //
    // The gradient will be clipped to your original path.
    // You could use other fill effects like patterns here.
    //
    CGContextDrawLinearGradient(
      context, 
      yourGradient, 
      gradientTop, 
      gradientBottom, 
      0
    );

Further notes

It's worth emphasising part of the documentation above:

值得强调的是上面的部分文档:

Quartz creates a stroked path using the parameters of the current graphics context.

Quartz使用当前图形上下文的参数创建描边路径。

The obvious parameter is the line width. However, all line drawing state is used, such as stroke pattern, mitre limit, line joins, caps, dash patterns, etc. This makes the approach extremely powerful.

明显的参数是线宽。但是,使用所有线条绘制状态,例如笔划图案,斜接限制,线条连接,大写字母,虚线图案等。这使得该方法非常强大。

For additional details see this answer of this S.O. question.

有关其他详细信息,请参阅此S.O.的答案。题。

#2


20  

After several tries I'm now sure that gradients doesn't affect strokes, so I think it's impossible to draw gradient lines with CGContextStrokePath(). For horizontal and vertical lines the solution is to use CGContextAddRect() instead, which fortunately is what I need. I replaced

经过几次尝试,我现在确定渐变不会影响笔画,所以我认为用CGContextStrokePath()绘制渐变线是不可能的。对于水平和垂直线,解决方案是使用CGContextAddRect(),幸运的是我需要的。我换了

CGContextMoveToPoint(context, x, y);
CGContextAddLineToPoint(context, x2, y2);
CGContextStrokePath(context);

with

CGContextSaveGState(context);
CGContextAddRect(context, CGRectMake(x, y, width, height));
CGContextClip(context);
CGContextDrawLinearGradient (context, gradient, startPoint, endPoint, 0);
CGContextRestoreGState(context);

and everything works fine. Thanks to Brad Larson for the crucial hint.

一切正常。感谢Brad Larson的关键暗示。

#3


8  

After you draw the line, you can call

画线后,你可以打电话

CGContextClip(context);

to clip further drawing to your line area. If you draw the gradient, it should now be contained within the line area. Note that you will need to use a clear color for your line if you just want the gradient to show, and not the line underneath it.

将进一步的绘图剪辑到您的线区域。如果绘制渐变,现在应该包含在线条区域内。请注意,如果您只想显示渐变而不是下面的线,则需要为线条使用清晰的颜色。

There is the possibility that a line will be too thin for your gradient to show up, in which case you can use CGContextAddRect() to define a thicker area.

线条可能太薄而无法显示渐变,在这种情况下,您可以使用CGContextAddRect()来定义更粗的区域。

I present a more elaborate example of using this context clipping in my answer here.

我在这里的答案中提供了一个更详细的使用此上下文剪辑的示例。

#4


8  

You can use Core Animation layers. You can use a CAShaperLayer for your line by settings its path property and then you can use a CAGradientLayer as a layer mask to your shape layer that will cause the line to fade.

您可以使用Core Animation图层。您可以通过设置其路径属性为您的线路使用CAShaperLayer,然后您可以使用CAGradientLayer作为形状图层的图层蒙版,这将导致线条淡化。

Replace your CGContext... calls with calls to CGPath... calls to create the line path. Set the path field on the layer using that path. Then in your gradient layer, specify the colors you want to use (probably black to white) and then set the mask to be the line layer like this:

用调用CGPath ...调用替换你的CGContext ...调用来创建行路径。使用该路径在图层上设置路径字段。然后在渐变图层中,指定要使用的颜色(可能是黑色到白色),然后将蒙版设置为线条图层,如下所示:

 [gradientLayer setMask:lineLayer];

What's cool about the gradient layer is that is allows you to specify a list of locations where the gradient will stop, so you can fade in and fade out. It only supports linear gradients, but it sounds like that may fit your needs.

渐变层有什么好处,它允许您指定渐变停止的位置列表,这样您就可以淡入和淡出。它只支持线性渐变,但听起来可能符合您的需求。

Let me know if you need clarification.

如果您需要澄清,请告诉我。

EDIT: Now that I think of it, just create a single CAGradientLayer that is the width/height of the line you desire. Specify the gradient colors (black to white or black to clear color) and the startPoint and endtPoints and it should give you what you need.

编辑:现在我想起来,只需创建一个CAGradientLayer,它是你想要的线的宽度/高度。指定渐变颜色(黑色到白色或黑色以清除颜色)以及startPoint和endtPoints,它应该为您提供所需的颜色。

#5


0  

CGContextMoveToPoint(context, frame.size.width-200, frame.origin.y+10);
CGContextAddLineToPoint(context, frame.size.width-200, 100-10);
CGFloat colors[16] = { 0,0, 0, 0,
    0, 0, 0, .8,
    0, 0, 0, .8,
    0, 0,0 ,0 };
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 4);

CGContextSaveGState(context);
CGContextAddRect(context, CGRectMake(frame.size.width-200,10, 1, 80));
CGContextClip(context);
CGContextDrawLinearGradient (context, gradient, CGPointMake(frame.size.width-200, 10), CGPointMake(frame.size.width-200,80), 0);
CGContextRestoreGState(context);

its work for me.

它为我工作。