mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			151 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Swift
		
	
		
		
			
		
	
	
			151 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Swift
		
	
| 
											6 years ago
										 | 
 | ||
|  | import CryptoSwift | ||
|  | 
 | ||
| 
											6 years ago
										 | extension String { | ||
|  |     func matches(_ regex: String) -> Bool { | ||
|  |         return self.range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
| 
											6 years ago
										 | private class RNG { | ||
| 
											6 years ago
										 |     private let int32Max = Int(Int32.max) // 2147483647 | ||
| 
											6 years ago
										 |      | ||
| 
											6 years ago
										 |     private var seed: Int | ||
|  |     private var initial: Int | ||
| 
											6 years ago
										 |      | ||
| 
											6 years ago
										 |     init(seed: Int) { | ||
| 
											6 years ago
										 |         self.seed = seed % int32Max | ||
|  |         if (self.seed <= 0) { self.seed += int32Max - 1 } | ||
| 
											6 years ago
										 |         self.initial = self.seed | ||
|  |     } | ||
|  |      | ||
| 
											6 years ago
										 |     func next() -> Int { | ||
| 
											6 years ago
										 |         // Casting to Int64 incase number goes above Int32 | ||
| 
											6 years ago
										 |         let seed = (Int64(self.seed) * 16807) % Int64(int32Max) | ||
| 
											6 years ago
										 |         self.seed = Int(seed) | ||
|  |         return self.seed | ||
| 
											6 years ago
										 |     } | ||
|  |      | ||
|  |     func nextFloat() -> Float { | ||
| 
											6 years ago
										 |         return Float(next() - 1) / Float(int32Max - 1) | ||
| 
											6 years ago
										 |     } | ||
|  |      | ||
|  |     func nextCGFloat() -> CGFloat { | ||
|  |         return CGFloat(nextFloat()) | ||
|  |     } | ||
|  |      | ||
|  |     func reset() { | ||
|  |         seed = initial | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | public class JazzIcon { | ||
|  |     private let generator: RNG | ||
|  |      | ||
|  |     // Colour palette | ||
|  |     private var colours: [UIColor] = [ | ||
|  |         0x01888c, // Teal | ||
|  |         0xfc7500, // bright orange | ||
|  |         0x034f5d, // dark teal | ||
|  |         0xE784BA, // light pink | ||
|  |         0x81C8B6, // bright green | ||
|  |         0xc7144c, // raspberry | ||
|  |         0xf3c100, // goldenrod | ||
|  |         0x1598f2, // lightning blue | ||
|  |         0x2465e1, // sail blue | ||
|  |         0xf19e02, // gold | ||
|  |     ].map { UIColor(rgb: $0) } | ||
|  |      | ||
|  |     // Defaults | ||
|  |     private let shapeCount = 4 | ||
|  |     private let wobble = 30 | ||
|  |      | ||
| 
											6 years ago
										 |     init(seed: Int, colours: [UIColor]? = nil) { | ||
| 
											6 years ago
										 |         self.generator = RNG(seed: seed) | ||
|  |         if let colours = colours { | ||
|  |             self.colours = colours | ||
|  |         } | ||
|  |     } | ||
|  |      | ||
|  |     convenience init(seed: String, colours: [UIColor]? = nil) { | ||
| 
											6 years ago
										 |         // Ensure we have a correct hash | ||
|  |         var hash = seed | ||
|  |         if !hash.matches("^[0-9A-Fa-f]+$") || hash.count < 12 { hash = seed.sha512() } | ||
|  |          | ||
| 
											6 years ago
										 |         guard let number = Int(hash.substring(to: 12), radix: 16) else { | ||
| 
											6 years ago
										 |             owsFailDebug("[JazzIcon] Failed to generate number from seed string: \(seed)") | ||
|  |             self.init(seed: 1234, colours: colours) | ||
|  |             return | ||
|  |         } | ||
|  |          | ||
|  |         self.init(seed: number, colours: colours) | ||
|  |     } | ||
|  |      | ||
|  |     public func generateLayer(ofSize diameter: CGFloat) -> CALayer { | ||
|  |         generator.reset() | ||
|  |          | ||
|  |         let newColours = hueShift(colours: colours) | ||
|  |         let shuffled = shuffle(newColours) | ||
|  |          | ||
|  |         let base = getSquareLayer(with: diameter, colour: shuffled[0].cgColor) | ||
|  |         base.masksToBounds = true | ||
|  |          | ||
|  |         for index in 0..<shapeCount { | ||
|  |             let layer = generateShapeLayer(diameter: diameter, colour: shuffled[index + 1].cgColor, index: index, total: shapeCount - 1) | ||
|  |             base.addSublayer(layer) | ||
|  |         } | ||
|  |          | ||
|  |         return base | ||
|  |     } | ||
|  |      | ||
|  |     private func getSquareLayer(with diameter: CGFloat, colour: CGColor? = nil) -> CAShapeLayer { | ||
|  |         let frame = CGRect(x: 0, y: 0, width: diameter, height: diameter) | ||
|  |          | ||
|  |         let layer = CAShapeLayer() | ||
|  |         layer.frame = frame | ||
|  |         layer.path = UIBezierPath(roundedRect: frame, cornerRadius: 0).cgPath | ||
|  |         layer.fillColor = colour | ||
|  |         return layer | ||
|  |     } | ||
|  |      | ||
|  |     private func generateShapeLayer(diameter: CGFloat, colour: CGColor, index: Int, total: Int) -> CALayer { | ||
|  |         let center = diameter / 2 | ||
|  |         let firstRotation = generator.nextCGFloat() | ||
|  |         let angle = CGFloat.pi * 2 * firstRotation | ||
|  |          | ||
|  |         let a = diameter / CGFloat(total) | ||
|  |         let b: CGFloat = generator.nextCGFloat() | ||
|  |         let c = CGFloat(index) * a | ||
|  |         let velocity = a * b + c | ||
|  |         let translation = CGPoint(x: cos(angle) * velocity, y: sin(angle) * velocity) | ||
|  |      | ||
|  |         // Third random is a shape rotation ontop of all that | ||
|  |         let secondRotation = generator.nextCGFloat() | ||
|  |         let rotation = (firstRotation * 360.0) + (secondRotation * 180) | ||
|  |         let radians = rotation.rounded(toPlaces: 1) * CGFloat.pi / 180.0 | ||
|  |          | ||
|  |         let layer = getSquareLayer(with: diameter, colour: colour) | ||
|  |         layer.position = CGPoint(x: center + translation.x, y: center + translation.y) | ||
|  |         layer.transform = CATransform3DMakeRotation(radians, 0, 0, center) | ||
|  |          | ||
|  |         return layer | ||
|  |     } | ||
|  |      | ||
|  |     private func shuffle<T>(_ array: [T]) -> [T] { | ||
|  |         var currentIndex = array.count | ||
|  |         var mutated = array | ||
|  |         while (currentIndex > 0) { | ||
| 
											6 years ago
										 |             let randomIndex = generator.next() % currentIndex | ||
| 
											6 years ago
										 |             currentIndex -= 1 | ||
|  |             mutated.swapAt(currentIndex, randomIndex) | ||
|  |         } | ||
|  |         return mutated | ||
|  |     } | ||
|  |      | ||
|  |     private func hueShift(colours: [UIColor]) -> [UIColor] { | ||
|  |         let amount = generator.nextCGFloat() * 30 - CGFloat(wobble / 2); | ||
| 
											6 years ago
										 |         return colours.map { $0.adjustHue(by: amount) } | ||
| 
											6 years ago
										 |     } | ||
|  | } |