1

I currently have a function where I draw a QR code

+ (void)drawQRCode:(QRcode *)code context:(CGContextRef)ctx size:(CGFloat)size {}

Currently, I can set the color including the colorspace using the following:

CGContextSetFillColorWithColor(ctx, [theColor colorUsingColorSpaceName:NSCalibratedWhiteColorSpace].CGColor);

I am trying to implement the ability to create a gradient I've converted two NSColors to an NSGradient - there doesn't appear to be any way of getting a gradient as an NSColor so I'm wondering what would be the best way of setting the fill of the context with a gradient?

Thanks in advance for any suggestions!

Hakan Dilek
  • 2,178
  • 2
  • 23
  • 35

1 Answers1

1

CGContext has drawLinearGradient

https://developer.apple.com/documentation/coregraphics/cgcontext/1454782-drawlineargradient

and drawRadialGradient

https://developer.apple.com/documentation/coregraphics/cgcontext/1455923-drawradialgradient

To use them to fill a shape, put the shape's path into the context, then use the shape as a clipping path before drawing the gradient.

Here's a drawRect from a view that demonstrates the technique:

- (void) drawRect: (CGRect) rect {
    CGColorRef myColors[3] = {
        [[UIColor redColor] CGColor],
        [[UIColor yellowColor] CGColor],
        [[UIColor blueColor] CGColor] };

    CGFloat locations[3] = { 0.0, 0.25, 1.0 };

    CGRect circleRect = CGRectInset([self bounds], 20, 20);
    CGFloat circleRadius = circleRect.size.width / 2.0;

    CGContextRef cgContext = UIGraphicsGetCurrentContext();

    CGContextSaveGState(cgContext);
    CGContextSetFillColorWithColor(cgContext, [[UIColor blackColor] CGColor]);
    CGContextFillRect(cgContext, [self bounds]);
    CGContextRestoreGState(cgContext);

    CGContextSaveGState(cgContext);
    CGContextAddEllipseInRect(cgContext, circleRect);
    CGContextClip(cgContext);

    CGContextTranslateCTM(cgContext, CGRectGetMidX(circleRect), CGRectGetMidY(circleRect));
    CGContextRotateCTM(cgContext, M_PI / 3.0);


    CFArrayRef colors = CFArrayCreate(nil, (const void**) myColors, 3, &kCFTypeArrayCallBacks);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, colors, locations);
    CFRelease(colors);
    CFRelease(colorSpace);

    CGContextDrawLinearGradient(cgContext, gradient, CGPointMake(-circleRadius, 0),
                                CGPointMake(circleRadius, 0), kCGGradientDrawsAfterEndLocation + kCGGradientDrawsBeforeStartLocation);
    CFRelease(gradient);
    CGContextRestoreGState(cgContext);
}

Here's an equivalent playground in Swift.

import UIKit
import PlaygroundSupport

class CircleGradientView : UIView {
    override func draw(_ rect: CGRect) {

        let colors  = [
            UIColor.red.cgColor,
            UIColor.yellow.cgColor, // Yellow
            UIColor.blue.cgColor  // Blue
        ]

        let colorSpace = CGColorSpace(name: CGColorSpace.sRGB)!


        let locations : [CGFloat] = [0.0, 0.25, 1.0]
        let gradient = CGGradient(colorsSpace: colorSpace,
                                  colors: colors as CFArray,
                                  locations: locations)

        if let context = UIGraphicsGetCurrentContext() {
            let circleRect = self.bounds.insetBy(dx: 20, dy: 20)
            let circleRadius = circleRect.width / 2.0

            context.saveGState()
            context.addEllipse(in: circleRect)
            context.clip()

            context.translateBy(x: circleRect.midX, y: circleRect.midY)
            context.rotate(by: .pi / 3.0)

            context.drawLinearGradient(gradient!,
                                       start: CGPoint(x: -circleRadius, y: 0),
                                       end: CGPoint(x: circleRadius, y: 0),
                                       options: [.drawsAfterEndLocation, .drawsBeforeStartLocation])
           context.restoreGState()
        }
    }
}

let myView = CircleGradientView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
PlaygroundSupport.PlaygroundPage.current.liveView = myView
Scott Thompson
  • 22,629
  • 4
  • 32
  • 34
  • Perfect just what I needed to get started thanks Scott :) – GeraldTheGerm Oct 26 '21 at 15:09
  • Hey Scott, Still working away at this, I have managed to get it 'working' but not in the best way so far i've managed to covert the gradient into an NSImage and then use the following to put the gradient onto the QR: CGContextSetFillColorWithColor(ctx, [NSColor colorWithPatternImage:img].CGColor); However this is sluggish and not an ideal solution - i've been playing with your examples, but am not sure how to get the Path of the QR in order to put the gradient onto the QR rather than the background, any ideas? https://imgur.com/9hZAWCI – GeraldTheGerm Oct 28 '21 at 15:34
  • How are you drawing the QR code? – Scott Thompson Oct 28 '21 at 15:39
  • That simple question actually helped me! I was putting the code in the wrong place not where the QR was being drawn - feel a little stupid but at the same time am learning so much! One last question - I know have a gradient which I like: `CGContextDrawLinearGradient(ctx, gradient, CGPointMake(0, 45), CGPointMake(2500, 0), kCGGradientDrawsAfterEndLocation + kCGGradientDrawsBeforeStartLocation);` I'd like to create a slider to change the angle of the gradient so i'd assume the angle is the number which is currently 45, how do I translate this so the slider can rotate it between 0 & 360° – GeraldTheGerm Oct 28 '21 at 18:17
  • Look at the example code I sent. The gradient takes a start point and and end point that tells Quartz where to start and end the gradient. I wanted my gradient rotated (pi/3 radians or 60˚ in this case). But I didn't want to calculate the start point and end point that corresponded to "rotated 60 degrees", so I translated the origin to the center point, rotated the coordinates by 60 degrees, and drew with a start and end point that was on the (newly rotated) axes. I was lazy and didn't want to calculate the start and end points for the angle. – Scott Thompson Oct 28 '21 at 20:25