iOS Find Color at Point Between Two Colors

喜夏-厌秋 提交于 2019-11-27 04:24:31

问题


I have a problem: I need to be able to take two colors and make a 'virtual gradient' out of them. I then need to be able to find the color at any point on this line. My current approach is this:

if (fahrenheit < kBottomThreshold)
{
    return [UIColor colorWithRed:kBottomR/255.0f green:kBottomG/255.0f blue:kBottomB/255.0f alpha:1];
}
if (fahrenheit > kTopThreshold)
{
    return [UIColor colorWithRed:kTopR/255.0f green:kTopG/255.0f blue:kTopB/255.0f alpha:1];
}

double rDiff = kTopR - kBottomR;
double gDiff = kTopG - kBottomG;
double bDiff = kTopB - kBottomB;

double tempDiff = kTopThreshold - kBottomThreshold;

double rValue;
double gValue;
double bValue;

rValue = kBottomR + ((rDiff/tempDiff) * fahrenheit);
gValue = kBottomG + ((gDiff/tempDiff) * fahrenheit);
bValue = kBottomB + ((bDiff/tempDiff) * fahrenheit);

return [UIColor colorWithRed:rValue/255.0f green:gValue/255.0f blue:bValue/255.0f alpha:1];

Variables:

  • fahrenheit is a variable passed into my function that is the number on this virtual line that I want to find the color for.
  • kTopR, kTopB, and kTopG are the RGB values for one end of the gradient. Same for their kBottom counterparts.
  • kBottomThreshold and kTopThreshold are the endpoints of my gradient.

Here's my problem: When fahrenheit goes over either end of the gradient, the gradient seems to 'jump' to a different value.

I've included an example project, hosted on my S3 server, here.

You really need to download the project and try it on the simulator/device to see what I mean (unless you are crazy smart and can tell just by looking at the code)


回答1:


The problem is that you're not subtracting kBottomThreshold from farenheit.

But let's simplify.

First, we want to map the input temperature to a parameter t in the range [0 ... 1]. Then, we want to map t to an output in the range [kBottomR ... kTopR], and also to an output in the range [kBottomG ... kTopG], and also to an output in the range [kBottomB ... kTopB].

UIColor *colorForDegreesFahrenheit(double fahrenheit) {
    double t = (fahrenheit - kBottomThreshold) / (kTopThreshold - kBottomThreshold);

    // Clamp t to the range [0 ... 1].
    t = MAX(0.0, MIN(t, 1.0));

    double r = kBottomR + t * (kTopR - kBottomR);
    double g = kBottomG + t * (kTopG - kBottomG);
    double b = kBottomB + t * (kTopB - kBottomB);

    return [UIColor colorWithRed:r/255 green:g/255 blue:b/255 alpha:1];
}



回答2:


Swift - 3.0 && 4.0

extension UIColor {
    func toColor(_ color: UIColor, percentage: CGFloat) -> UIColor {
        let percentage = max(min(percentage, 100), 0) / 100
        switch percentage {
        case 0: return self
        case 1: return color
        default:
            var (r1, g1, b1, a1): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
            var (r2, g2, b2, a2): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
            guard self.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) else { return self }
            guard color.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) else { return self }

            return UIColor(red: CGFloat(r1 + (r2 - r1) * percentage),
                           green: CGFloat(g1 + (g2 - g1) * percentage),
                           blue: CGFloat(b1 + (b2 - b1) * percentage),
                           alpha: CGFloat(a1 + (a2 - a1) * percentage))
        }
    }
}

Usage:-

let colorRed = UIColor.red
let colorBlue = UIColor.blue

let colorOutput = colorRed.toColor(colorBlue, percentage: 50)

Result




回答3:


In case your gradient is more complex than a 2 color gradient, you may consider drawing a CGGradientRef into a temporary CGImageRef and directly read RGBA values from the image buffer.

Here is something that I had to do with a 5 gradient stops and colors:

    CGFloat tmpImagewidth = 1000.0f; // Make this bigger or smaller if you need more or less resolution (number of different colors).
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    // create a gradient
    CGFloat locations[] = { 0.0,
        0.35,
        0.55,
        0.8,
        1.0 };
    NSArray *colors = @[(__bridge id) [UIColor redColor].CGColor,
                        (__bridge id) [UIColor greenColor].CGColor,
                        (__bridge id) [UIColor blueColor].CGColor,
                        (__bridge id) [UIColor yellowColor].CGColor,
                        (__bridge id) [UIColor redColor].CGColor,
                        ];
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations);
    CGPoint startPoint = CGPointMake(0, 0);
    CGPoint endPoint = CGPointMake(tmpImagewidth, 0);

    // create a bitmap context to draw the gradient to, 1 pixel high.
    CGContextRef context = CGBitmapContextCreate(NULL, tmpImagewidth, 1, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);

    // draw the gradient into it
    CGContextAddRect(context, CGRectMake(0, 0, tmpImagewidth, 1));
    CGContextClip(context);
    CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);

    // Get our RGB bytes into a buffer with a couple of intermediate steps...
    //      CGImageRef -> CFDataRef -> byte array
    CGImageRef cgImage = CGBitmapContextCreateImage(context);
    CGDataProviderRef provider = CGImageGetDataProvider(cgImage);
    CFDataRef pixelData = CGDataProviderCopyData(provider);

    // cleanup:
    CGGradientRelease(gradient);
    CGColorSpaceRelease(colorSpace);
    CGImageRelease(cgImage);
    CGContextRelease(context);

    const UInt8* data = CFDataGetBytePtr(pixelData);

    // we got all the data we need.
    // bytes in the data buffer are a succession of R G B A bytes

    // For instance, the color of the point 27% in our gradient is:
    CGFloat x = tmpImagewidth * .27;
    int pixelIndex = (int)x * 4; // 4 bytes per color
    UIColor *color = [UIColor colorWithRed:data[pixelIndex + 0]/255.0f
                                     green:data[pixelIndex + 1]/255.0f
                                      blue:data[pixelIndex + 2]/255.0f
                                     alpha:data[pixelIndex + 3]/255.0f];

    // done fetching color data, finally release the buffer
    CGDataProviderRelease(provider);

I am not saying this is better than the "math way" in the answer above, certainly there is a memory and cpu tax that goes into producing the temporary image. The advantage of this however, is that the code complexity stays the same no matter how many gradient stops you need...




回答4:


I would like to extend/modify @Sebastien Windal´s answer, as his answer works great for my use case, but can be further improved by getting the pixelData directly from the context (see Get pixel data as array from UIImage/CGImage in swift).

I implemented his answer in swift, therefore the changes are also written in swift.

Just pass the Int Array to the context before drawing the gradient. The array will then be filled with the pixelData when drawing the context.

let dataSize = tmpImagewidth * 1 * 4
var pixelData = [UInt8](repeating: 0, count: Int(dataSize))


let context = CGContext(data: &pixelData, width: Int(tmpImagewidth), height: 1, bitsPerComponent: 8, bytesPerRow: 4 * Int(tmpImagewidth), space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)

Then we can skip creating an image first to read the pixelData from there.



来源:https://stackoverflow.com/questions/15032562/ios-find-color-at-point-between-two-colors

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!