CGContextDrawAngleGradient?

2019-01-13 17:02发布

问题:

Dipping my feet into some more Core Graphics drawing, I'm attempting to recreate a wicked looking metallic knob, and I've landed on what is probably a show-stopping issue.

There doesn't seem to be any way to draw angle gradients in Core Graphics. I see there's CGContextDrawRadialGradient() and CGContextDrawLinearGradient(), but there's nothing that I see that would allow me to draw an angle gradient. Does anyone know of a workaround, or a bit of framework hidden away somewhere to accomplish this without pre-rendering the knob into an image file?

AngleGradientKnob http://dl.dropbox.com/u/3009808/AngleGradient.png.

回答1:

This is kind of thrown together, but it's the approach I'd probably take. This is creating an angle gradient by drawing it directly into a bitmap using some simple trig, then clipping it to a circle. I create a grid of memory using a grayscale colorspace, calculate the angle from a given point to the center, and then color that based on a periodic function, running between 0 to 255. You could of course expand this to do RGBA color as well.

Of course you'd cache this and play with the math to get the colors you want. This currently runs all the way from black to white, which doesn't look as good as you'd like.

- (void)drawRect:(CGRect)rect {
  CGImageAlphaInfo alphaInfo = kCGImageAlphaNone;
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
  size_t components = CGColorSpaceGetNumberOfComponents( colorSpace );
  size_t width = 100;
  size_t height = 100;
  size_t bitsPerComponent = 8;
  size_t bytesPerComponent = bitsPerComponent / 8;
  size_t bytesPerRow = width * bytesPerComponent * components;
  size_t dataLength = bytesPerRow * height;

  uint8_t data[dataLength];

  CGContextRef imageCtx = CGBitmapContextCreate( &data, width, height, bitsPerComponent,
                                      bytesPerRow, colorSpace, alphaInfo );

  NSUInteger offset = 0;
  for (NSUInteger y = 0; y < height; ++y) {
    for (NSUInteger x = 0; x < bytesPerRow; x += components) {
      CGFloat opposite = y - height/2.;
      CGFloat adjacent = x - width/2.;
      if (adjacent == 0) adjacent = 0.001;
      CGFloat angle = atan(opposite/adjacent);
      data[offset] = abs((cos(angle * 2) * 255));
      offset += components * bytesPerComponent;
    }
  }

  CGImageRef image = CGBitmapContextCreateImage(imageCtx);

  CGContextRelease(imageCtx);
  CGColorSpaceRelease(colorSpace);

  CGContextRef ctx = UIGraphicsGetCurrentContext();

  CGRect buttonRect = CGRectMake(100, 100, width, width);
  CGContextAddEllipseInRect(ctx, buttonRect);
  CGContextClip(ctx);

  CGContextDrawImage(ctx, buttonRect, image);
  CGImageRelease(image);
}


回答2:

To expand on what's in the comments to the accepted answer, here's the code for generating an angle gradient using Core Image. This should work in iOS 8 or later.

// generate a dummy image of the required size
UIGraphicsBeginImageContextWithOptions(CGSizeMake(256.0, 256.0), NO, [[UIScreen mainScreen] scale]);
CIImage *dummyImage = [CIImage imageWithCGImage:UIGraphicsGetImageFromCurrentImageContext().CGImage];

// define the kernel algorithm
NSString *kernelString = @"kernel vec4 circularGradientKernel(__color startColor, __color endColor, vec2 center, float radius) { \n"
"    vec2 point = destCoord() - center;"
"    float rsq = point.x * point.x + point.y * point.y;"
"    float theta = mod(atan(point.y, point.x), radians(360.0));"
"    return (rsq < radius*radius) ? mix(startColor, endColor, 0.5+0.5*cos(4.0*theta)) : vec4(0.0, 0.0, 0.0, 1.0);"
"}";

// initialize a Core Image context and the filter kernel
CIContext *context = [CIContext contextWithOptions:nil];
CIColorKernel *kernel = [CIColorKernel kernelWithString:kernelString];

// argument array, corresponding to the first line of the kernel string
NSArray *args = @[ [CIColor colorWithRed:0.5 green:0.5 blue:0.5],
                   [CIColor colorWithRed:1.0 green:1.0 blue:1.0],
                   [CIVector vectorWithCGPoint:CGPointMake(CGRectGetMidX(dummyImage.extent),CGRectGetMidY(dummyImage.extent))],
                   [NSNumber numberWithFloat:200.0]];

// apply the kernel to our dummy image, and convert the result to a UIImage
CIImage *ciOutputImage = [kernel applyWithExtent:dummyImage.extent arguments:args];
CGImageRef cgOutput = [context createCGImage:ciOutputImage fromRect:ciOutputImage.extent];
UIImage *gradientImage = [UIImage imageWithCGImage:cgOutput];
CGImageRelease(cgOutput);

This generates the following image: