如何在iOS中如此绘制程序化渐变?
目前我只找到了从圆圈的中心到边缘创建渐变的代码(另外只有圆圈)。
答案 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
}
}