iOS - 带角度渐变的圆圈

时间:2014-10-26 13:29:55

标签: ios view gradient geometry

如何在iOS中如此绘制程序化渐变?

目前我只找到了从圆圈的中心到边缘创建渐变的代码(另外只有圆圈)。

1 个答案:

答案 0 :(得分:11)

如果你想要这样的渐变,你可能必须自己实现它。例如,这会创建一个像素缓冲数据提供程序,在那种扫描径向模式中填充alpha / red / green / blue数据,并从中创建一个图像:

- (UIImage *)buildAngularGradientInRect:(CGRect)rect
                                 radius:(CGFloat)radius
                             startAngle:(CGFloat)startAngle
                               endAngle:(CGFloat)endAngle
                             startColor:(UIColor *)startColor
                               endColor:(UIColor *)endColor
                              clockwise:(BOOL)clockwise
                                  scale:(CGFloat)scale
{
    if (scale == 0) scale = [[UIScreen mainScreen] scale];

    CGFloat startRed, startGreen, startBlue, startAlpha;
    [startColor getRed:&startRed green:&startGreen blue:&startBlue alpha:&startAlpha];

    CGFloat endRed, endGreen, endBlue, endAlpha;
    [endColor getRed:&endRed green:&endGreen blue:&endBlue alpha:&endAlpha];

    size_t width = rect.size.width * scale;
    size_t height = rect.size.height * scale;
    CGPoint center = CGPointMake(width / 2.0, height / 2.0);
    size_t bufferSize = width * height * 4;
    NSMutableData *data = [NSMutableData dataWithCapacity:bufferSize];
    struct {
        UInt8 alpha;
        UInt8 red;
        UInt8 green;
        UInt8 blue;
    } argb;

    size_t bitsPerComponent = 8;
    size_t bitsPerPixel = 4 * bitsPerComponent;
    size_t bytesPerRow = 4 * width;

    for (NSInteger y = 0; y < height; y++) {
        for (NSInteger x = 0; x < width; x++) {
            CGFloat angle = atan2(y - center.y, x - center.x);
            CGFloat distance = hypot(y - center.y, x - center.x);
            CGFloat value = [self percentAngle:angle betweenStart:startAngle end:endAngle clockwise:clockwise];
            if (distance <= (radius * scale) && value >= 0.0 && value <= 1.0) {
                argb.alpha = (startAlpha + (endAlpha - startAlpha) * value) * 255;
                argb.red   = (startRed   + (endRed   - startRed)   * value) * 255;
                argb.green = (startGreen + (endGreen - startGreen) * value) * 255;
                argb.blue  = (startBlue  + (endBlue  - startBlue)  * value) * 255;
            } else {
                argb.alpha = 0;
                argb.red   = 255;
                argb.green = 255;
                argb.blue  = 255;
            }
            [data appendBytes:&argb length:sizeof(argb)];
        }
    }

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
    CGImageRef imageRef = CGImageCreate(width,
                                        height,
                                        bitsPerComponent,
                                        bitsPerPixel,
                                        bytesPerRow,
                                        colorSpace,
                                        (CGBitmapInfo)kCGImageAlphaPremultipliedFirst,
                                        provider,
                                        NULL,
                                        NO,
                                        kCGRenderingIntentDefault);

    CGColorSpaceRelease(colorSpace);
    CGDataProviderRelease(provider);
    UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
    CGImageRelease(imageRef);

    return image;
}

- (CGFloat)percentAngle:(CGFloat)angle betweenStart:(CGFloat)start end:(CGFloat)end clockwise:(BOOL)clockwise
{
    while (start < 0) start += M_PI * 2.0;
    while (angle < 0) angle += M_PI * 2.0;
    while (end < 0)   end += M_PI * 2.0;
    if (clockwise) {
        while (end < start) end += M_PI * 2.0;
        while (angle < start) angle += M_PI * 2.0;
    } else {
        while (start < end) end -= M_PI * 2.0;
        while (start < angle) angle -= M_PI * 2.0;
    }
    CGFloat range = end - start;
    CGFloat value = angle - start;

    return value / range;
}

因此,您可以创建一个简单的UIImage

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
[self.view addSubview:imageView];
UIImage *image = [self buildAngularGradientInRect:imageView.bounds
                                           radius:imageView.bounds.size.width / 2.0
                                       startAngle:M_PI + M_PI_4
                                         endAngle:M_PI + M_PI_2 + M_PI_4
                                       startColor:[UIColor whiteColor]
                                         endColor:[UIColor redColor]
                                        clockwise:YES
                                            scale:0];
imageView.image = image;

或者,如您的示例中所示,创建一个使用Alpha通道的渐变,然后将其作为蒙版应用于另一个视图:

UIImage *image = [self buildAngularGradientInRect:view.bounds
                                           radius:view.bounds.size.width / 2.0
                                       startAngle:M_PI + M_PI_4
                                         endAngle:M_PI + M_PI_2 + M_PI_4
                                       startColor:[UIColor clearColor]
                                         endColor:[UIColor whiteColor]
                                        clockwise:YES
                                            scale:0];

CALayer *maskLayer = [CALayer layer];
maskLayer.frame = view.bounds;
maskLayer.contents = (id)image.CGImage;
view.layer.mask = maskLayer;

希望这说明了可以应用于图像或蒙版的像素级控制。


FWIW,这是Swift 3的演绎:

func buildAngularGradient(in rect: CGRect, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, startColor: UIColor, endColor: UIColor, clockwise: Bool = true, scale: CGFloat = UIScreen.main.scale) -> UIImage? {
    var startRed: CGFloat = 0
    var startGreen: CGFloat = 0
    var startBlue: CGFloat = 0
    var startAlpha: CGFloat = 0
    startColor.getRed(&startRed, green: &startGreen, blue: &startBlue, alpha: &startAlpha)

    var endRed: CGFloat = 0
    var endGreen: CGFloat = 0
    var endBlue: CGFloat = 0
    var endAlpha: CGFloat = 0
    endColor.getRed(&endRed, green: &endGreen, blue: &endBlue, alpha: &endAlpha)

    let width = Int(rect.size.width * scale)
    let height = Int(rect.size.height * scale)
    let center = CGPoint(x: width / 2, y: height / 2)

    let space = CGColorSpaceCreateDeviceRGB()
    let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: width * 4, space: space, bitmapInfo: Pixel.bitmapInfo)!

    let buffer = context.data!

    let pixels = buffer.bindMemory(to: Pixel.self, capacity: width * height)
    var pixel: Pixel
    for y in 0 ..< height {
        for x in 0 ..< width {
            let angle = atan2(CGFloat(y) - center.y, CGFloat(x) - center.x)
            let distance = hypot(CGFloat(y) - center.y, CGFloat(x) - center.x)

            let value = percent(angle: angle, between: startAngle, and: endAngle, clockwise: true)

            //    CGFloat value = [self percentAngle:angle betweenStart:startAngle end:endAngle clockwise:clockwise];
            if distance <= (radius * scale) && value >= 0.0 && value <= 1.0 {
                pixel = Pixel(red:   UInt8((startRed   + (endRed   - startRed)   * value) * 255),
                              green: UInt8((startGreen + (endGreen - startGreen) * value) * 255),
                              blue:  UInt8((startBlue  + (endBlue  - startBlue)  * value) * 255),
                              alpha: UInt8((startAlpha + (endAlpha - startAlpha) * value) * 255))
            } else {
                pixel = Pixel(red: 255, green: 255, blue: 255, alpha: 0)
            }
            pixels[y * width + x] = pixel
        }
    }

    let cgImage = context.makeImage()!
    return UIImage(cgImage: cgImage, scale: scale, orientation: .up)
}

private func percent(angle: CGFloat, between start: CGFloat, and end: CGFloat, clockwise: Bool) -> CGFloat {
    var start = start

    while start < 0 { start += .pi * 2 }

    var angle = angle
    while angle < 0 { angle += .pi * 2 }

    var end = end
    while end < 0 { end += .pi * 2 }

    if clockwise {
        while (end < start) { end += .pi * 2 }
        while (angle < start) { angle += .pi * 2 }
    } else {
        while (start < end) { end -= .pi * 2 }
        while (start < angle) { angle -= .pi * 2 }
    }
    let range = end - start
    let value = angle - start

    return value / range;
}

其中

struct Pixel: Equatable {
    private var rgba: UInt32

    var red: UInt8 {
        return UInt8((rgba >> 24) & 255)
    }

    var green: UInt8 {
        return UInt8((rgba >> 16) & 255)
    }

    var blue: UInt8 {
        return UInt8((rgba >> 8) & 255)
    }

    var alpha: UInt8 {
        return UInt8((rgba >> 0) & 255)
    }

    init(red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8) {
        rgba = (UInt32(red) << 24) | (UInt32(green) << 16) | (UInt32(blue) << 8) | (UInt32(alpha) << 0)
    }

    static let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue

    static func ==(lhs: Pixel, rhs: Pixel) -> Bool {
        return lhs.rgba == rhs.rgba
    }
}