【问题标题】:iOS Find Color at Point Between Two ColorsiOS 在两种颜色之间的点处查找颜色
【发布时间】:2013-02-08 13:34:34
【问题描述】:

我有一个问题:我需要能够采用两种颜色并从中制作“虚拟渐变”。然后我需要能够在这条线上的任何点找到颜色。我目前的做法是这样的:

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];

变量:

  • fahrenheit 是传递给我的函数的变量,它是我要为其查找颜色的虚拟行上的数字。
  • kTopRkTopBkTopG 是渐变一端的 RGB 值。他们的kBottom 同行也是如此。
  • kBottomThresholdkTopThreshold 是我渐变的端点。

这是我的问题: fahrenheit 越过渐变的任一端时,渐变似乎“跳跃”到不同的值。 em>

我包含了一个示例项目,托管在我的 S3 服务器上,here

你真的需要下载该项目并在模拟器/设备上尝试一下,看看我的意思(除非你非常聪明,并且可以通过查看代码)

【问题讨论】:

    标签: ios gradient uicolor blending linear-gradients


    【解决方案1】:

    感谢@ramchandra-n我实现了扩展以按百分比从颜色数组中获取中间颜色

    extension Array where Element: UIColor {
        func intermediate(percentage: CGFloat) -> UIColor {
            let percentage = Swift.max(Swift.min(percentage, 100), 0) / 100
            switch percentage {
            case 0: return first ?? .clear
            case 1: return last ?? .clear
            default:
                let approxIndex = percentage / (1 / CGFloat(count - 1))
                let firstIndex = Int(approxIndex.rounded(.down))
                let secondIndex = Int(approxIndex.rounded(.up))
                let fallbackIndex = Int(approxIndex.rounded())
                
                let firstColor = self[firstIndex]
                let secondColor = self[secondIndex]
                let fallbackColor = self[fallbackIndex]
                
                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 firstColor.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) else { return fallbackColor }
                guard secondColor.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) else { return fallbackColor }
                
                let intermediatePercentage = approxIndex - CGFloat(firstIndex)
                return UIColor(
                    red: CGFloat(r1 + (r2 - r1) * intermediatePercentage),
                    green: CGFloat(g1 + (g2 - g1) * intermediatePercentage),
                    blue: CGFloat(b1 + (b2 - b1) * intermediatePercentage),
                    alpha: CGFloat(a1 + (a2 - a1) * intermediatePercentage)
                )
            }
        }
    }
    

    您可以使用它来获得两种或多种颜色之间的中间颜色:

    let color = [.green, .yellow, .red].intermediate(percentage: 70)
    

    【讨论】:

    • 正是我想要的
    • 不错,完美运行!
    【解决方案2】:

    SwiftUI 颜色运算符 + 重载

     func +(lhs: Color, rhs: Color) -> Color {
        let lhsUIColor = UIColor(lhs)
        let rhsUIColor = UIColor(rhs)
        
        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 lhsUIColor.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) else { return Color(lhsUIColor) }
        guard rhsUIColor.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) else { return Color(rhsUIColor) }
    
        return Color(Color.RGBColorSpace.sRGB, red: Double(r1 + r2) / 2, green: Double(g1 + g2) / 2, blue: Double(b1 + b2) / 2, opacity: Double(a1 + a2) / 2)
    }
    

    【讨论】:

      【解决方案3】:

      斯威夫特 5.3

      获取两种颜色之间的n种颜色:

      import UIKit
      
      extension CGFloat {
          var getPercentageValues: [CGFloat] {
              let increment: CGFloat = 100/(self-1)
              var values = [CGFloat]()
              let last: CGFloat = 100
              var value: CGFloat = 0
              while value <= last {
                  values.append(value)
                  value += increment
              }
              return values
          }
      }
      
      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) = (.zero, .zero, .zero, .zero)
                  var (r2, g2, b2, a2): (CGFloat, CGFloat, CGFloat, CGFloat) = (.zero, .zero, .zero, .zero)
                  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))
              }
          }
          
          func getColors(to color: UIColor, with quantity: CGFloat) -> [UIColor] { 
              quantity.getPercentageValues.map { self.toColor(color, percentage: $0 ) }
          }
      }
      
      let redColor = UIColor.red
      let blueColor = UIColor.blue
      
      print(redColor.getColors(to: blueColor, with: 10))
      

      结果

      【讨论】:

        【解决方案4】:

        我想扩展/修改@Sebastien Windal 的回答,因为他的回答非常适合我的用例,但可以通过直接从上下文中获取 pixelData 来进一步改进(请参阅Get pixel data as array from UIImage/CGImage in swift)。

        我用 swift 实现了他的答案,因此更改也是用 swift 编写的。

        在绘制渐变之前将 Int 数组传递给上下文。然后,在绘制上下文时,该数组将使用 pixelData 填充。

        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)
        

        然后我们可以跳过创建图像先从那里读取pixelData。

        【讨论】:

          【解决方案5】:

          问题是您没有从farenheit 中减去kBottomThreshold

          但是让我们简化一下。

          首先,我们要将输入温度映射到 [0 ... 1] 范围内的参数t。然后,我们希望将t 映射到 [kBottomR ... kTopR] 范围内的输出,以及 [kBottomG ... kTopG] 范围内的输出,以及[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];
          }
          

          【讨论】:

            【解决方案6】:

            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))
                    }
                }
            }
            

            用法:-

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

            结果

            【讨论】:

            • 我能有一个客观的 c 版本吗?
            【解决方案7】:

            如果您的渐变比 2 色渐变更复杂,您可以考虑将 CGGradientRef 绘制到临时 CGImageRef 中并直接从图像缓冲区中读取 RGBA 值。

            这是我不得不做的 5 个渐变色标和颜色:

                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);
            

            我并不是说这比上面答案中的“数学方式”更好,当然,生成临时图像会产生内存和 CPU 税。 然而,这样做的好处是,无论您需要多少渐变停止,代码复杂度都保持不变......

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-07-06
              • 2012-11-27
              • 2011-08-26
              • 2019-12-07
              • 2014-09-14
              • 1970-01-01
              • 2013-08-02
              • 1970-01-01
              相关资源
              最近更新 更多