[英]How to generate a random but unique color for each UUID in Swift?
我想為每個 UUID 生成一個隨機但唯一的 UIColor。 目前,我正在使用此方法,但此方法不為 id 提供紅色/橙色/黃色。 更具體地說,我想生成一個像 WhatsApp 群聊這樣的配色方案,其中每個用戶都有一個獨特的標題顏色。
func getColorFromUUID(uuid:String) -> UIColor {
var hexa = ""
hexa += uuid.prefix(2)
let indexMid = uuid.index(uuid.startIndex, offsetBy: uuid.count/2 + 1)
let indexMidNext = uuid.index(uuid.startIndex, offsetBy: uuid.count/2 + 1)
let mid = String.init(uuid[indexMid])
let midNext = String.init(uuid[indexMidNext])
hexa += mid
hexa += midNext
hexa += uuid.suffix(2)
return self.hexStringToUIColor(hex: hexa)
}
func hexStringToUIColor (hex:String) -> UIColor {
var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
if (cString.hasPrefix("#")) {
cString.remove(at: cString.startIndex)
}
if ((cString.count) != 6) {
return UIColor.gray
}
var rgbValue:UInt32 = 0
Scanner(string: cString).scanHexInt32(&rgbValue)
return UIColor(
red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
alpha: CGFloat(1.0)
)
}
這是一個簡單的思考方式,顏色本質上是由 0 到 0xffffff 之間的數字表示的。 要從字符串中獲取 0 到 0xffffff 之間的數字,您可以獲取字符串的哈希碼和% 0x1000000
。 然后,您可以使用位掩碼提取 RGB:
func color(forUUID uuid: String) -> UIColor {
let hash = uuid.hash
let colorCode = abs(hash) % 0x1000000
let red = colorCode >> 16
let green = (colorCode >> 8) & 0xff
let blue = colorCode & 0xff
return UIColor(red: CGFloat(red) / 256, green: CGFloat(green) / 256, blue: CGFloat(blue) / 256, alpha: 1)
}
在評論中,您提到您只想要 256 種獨特的顏色。 在這種情況下, % 256
就可以了。 然后將結果傳遞給UIColor
的 HSB 初始化程序:
func color(forUUID uuid: String) -> UIColor {
let hash = uuid.hash
let hue = abs(hash) % 256
return UIColor(hue: CGFloat(hue) / 256, saturation: 1, brightness: 1, alpha: 1)
}
如果您正在尋找一種通過 UUID 着色的方法,以便可以輕松區分屏幕上的相鄰對象,那么您不需要為每個 UUID 設置唯一顏色。 我遇到了完全相同的問題,並發現不同的着色算法傾向於生成傾向於灰色的柔和顏色。
使用飽和度和值最大化的 HSV 着色方案,而不是使用 RGB 顏色值,將產生更多變化的充滿活力的結果。
我建議只使用 UUID 的單個字節來計算色調。 使用多於一個字節是使用的字節越多,獲得極值 0 或 1 的概率越低。使用的字節越多,該值越趨向於 0.5。
想想一對骰子 - 單個骰子是 1 的概率是 1/6。 並且每個值都有相同的機會。 如果你使用 2 個骰子(比如擲骰子),得到 2 的概率是 1/12,一個 3 是 2/12,一個 4 是 3/12,一個 5 是 4/12,一個 6 是 5/12, 7 是 6/12,8 是 5/12,9 是 4/12,10 是 3/12,11 是 2/12,12 是 1/12。 這給了我們一個鍾形曲線分布。 使用單個骰子可以讓我們獲得更均勻的分布。 將該邏輯應用於 UUID 問題,使用單個字節應該給我們一個更均勻的分布,每個值都有 1/256 的機會。
至少在理論上。 當我仔細觀察 UUID 時,我注意到每個 UUID 的第一個字節的絕對值總是 64 或更大。 然而,UUID 的第二個字節變化更大,分布更均勻。
使用 long 值(8 字節)生成此圖像:
final long lsb = Math.abs(val.getLeastSignificantBits());
final double colorGen = (double) lsb / Long.MAX_VALUE;
// the intent is to get an evenly distributed value between 0 and 1
final int rgb[] = ColorUtils.HSVtoRGB(colorGen, 1.0, 1.0);
return new Color(rgb[0], rgb[1], rgb[2]);
使用單個字節產生此圖像:
final long lsb = Math.abs(val.getLeastSignificantBits());
final ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES).putLong(lsb);
final byte[] bytes = buffer.array();
final double colorGen = Math.abs((short)bytes[1]) / 128.0;
final int rgb[] = ColorUtils.HSVtoRGB(colorGen, 1.0, 1.0);
return new Color(rgb[0], rgb[1], rgb[2]);
我不相信這是完全可能的。 至少不是你在問題和評論中描述它的方式。
如果我理解正確,您希望有“房間”或一群人,每個人都應該得到一種顏色,這樣就可以很好地區分誰寫了什么。 您似乎期望最多 256 人,但我猜您期望在一個組中。
如果用戶 UUID 用於確定用戶顏色,那么 256 人的限制根本沒有幫助。 當用戶在 A 組中時,他需要與其余 255 個人擁有不同的顏色。 當他在 B 組時,他需要的不同於該組中的 255 個人。 所以這意味着他仍然需要躲避您數據庫中的所有用戶。 如前所述,沒有足夠的顏色。
不過,一個解決方案是可以按房間執行此操作。 您可以在服務器或客戶端上執行此操作。 在客戶端上,類似以下內容應該可以工作:
class User {
let id: String = UUID().uuidString
}
class Room {
private var users: [User] = []
private var colors: [String: UIColor] = [String: UIColor]()
var availableColors: [UIColor] = {
let count: Int = 256
var colors: [UIColor] = []
for index in 0..<count {
let color = UIColor(hue: CGFloat(index)/CGFloat(count), saturation: 1.0, brightness: 1.0, alpha: 1.0)
colors.append(color)
}
return colors
}()
func addUser(_ user: User) {
users.append(user)
colors[user.id] = availableColors.remove(at: Int(arc4random())%availableColors.count)
}
func removeUser(_ user: User) {
if let color = colors[user.id] {
availableColors.append(color)
}
users = users.filter { $0.id != user.id }
}
func colorForUser(_ user: User) -> UIColor {
return colors[user.id] ?? UIColor.black
}
}
現在生成 256 種顏色,系統將為每個用戶隨機選擇一種顏色。 該系統可能仍然不是最好的,因為在 256 種顏色中,您可能已經擁有類似的顏色。 如果有 2 個人在一組並且兩個人的顏色相似,如果你問我,那會很糟糕(在這種情況下,它的機會相對較高)。
我寧願嘗試實際上只是隨機化第一個值然后躲避它。 考慮這樣的事情:
func generateRandomColorList(limit: Int = 256) -> [UIColor] {
var hues: [CGFloat] = [CGFloat]()
hues.append(CGFloat.random(in: 0...1)) // Initial value is random
while hues.count < limit {
let offset = 0.5/CGFloat(hues.count)
hues.forEach { hue in
let newHue = hue + offset
hues.append(newHue > 1.0 ? newHue - 1.0 : newHue) // If it gets past 1.0 just rotate it back
}
}
return hues.map { UIColor(hue: $0, saturation: 1.0, brightness: 1.0, alpha: 1.0) }
}
這個想法是,返回的數組隨機開始,然后排序,以便盡可能多地躲避當前值。 然后你可以像colors[user.id] = availableColors.removeFirst()
一樣使用它。 仍然會有一些可能的改進,比如每個軌道的加擾順序:
let offset = 0.5/CGFloat(hues.count)
var circle: [CGFloat] = []
hues.forEach { hue in
let newHue = hue + offset
circle.append(newHue > 1.0 ? newHue - 1.0 : newHue) // If it gets past 1.0 just rotate it back
}
hues.append(contentsOf: circle.randomlyOrdered())
如果您設法實現類似的功能,那么您實際上可能會開發出比我們心愛的 WhatsApp 更好的算法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.