簡體   English   中英

如何在 Swift 中為每個 UUID 生成隨機但唯一的顏色?

[英]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 組合比您可以使用 RGB(255*255*255) 創建的顏色數量多得多。 UUID 中的組合數為 2^128。 閱讀這里的uuid和部分色彩空間如何融入RGBA值這里

如果您正在尋找一種通過 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]);

使用 8 個字節

使用單個字節產生此圖像:

  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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM