我遇到了一个问题:我需要能够采用两种颜色并从中制作出“虚拟渐变”。然后,我需要能够在此行的任何位置找到颜色。我目前的做法是:
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
是一个传递给我的函数的变量,它是我要为其找到颜色的虚拟行上的数字。kTopR
,kTopB
和kTopG
是渐变一端的RGB值。他们的kBottom
同行也是如此。kBottomThreshold
和kTopThreshold
是我的渐变的终点。 以下是我的问题: 当fahrenheit
越过渐变的任何一端时,渐变似乎会“跳转”到不同的值。
我已经在我的S3服务器上托管了一个示例项目here。
你真的需要下载项目并在模拟器/设备上试一试,看看我的意思 (除非你疯狂聪明,只能看看代码)
答案 0 :(得分:14)
Swift - 3.0&amp;&amp; 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)
<强>结果强>
答案 1 :(得分:7)
问题是您没有从kBottomThreshold
中减去farenheit
。
但我们要简化。
首先,我们要将输入温度映射到[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];
}
答案 2 :(得分:3)
如果您的渐变比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税。 然而,这样做的好处是,无论您需要多少梯度停止,代码复杂性都保持不变......
答案 3 :(得分:1)
我想扩展/修改@Sebastien Windal的答案,因为他的答案对我的用例很有用,但可以通过直接从上下文获取pixelData来进一步改进(参见Get pixel data as array from UIImage/CGImage in swift)。 / p>
我在swift中实现了他的答案,因此这些变化也是迅速写的。
在绘制渐变之前,只需将Int Array传递给上下文。在绘制上下文时,数组将用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。
答案 4 :(得分: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)
答案 5 :(得分:1)
获取两种颜色之间的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))
答案 6 :(得分:0)
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)
}