BlurHashDecode.swift (4949B)
1 import UIKit 2 3 extension UIImage { 4 public convenience init?(blurHash: String, size: CGSize, punch: Float = 1) { 5 guard blurHash.count >= 6 else { return nil } 6 7 let sizeFlag = String(blurHash[0]).decode83() 8 let numY = (sizeFlag / 9) + 1 9 let numX = (sizeFlag % 9) + 1 10 11 let quantisedMaximumValue = String(blurHash[1]).decode83() 12 let maximumValue = Float(quantisedMaximumValue + 1) / 166 13 14 guard blurHash.count == 4 + 2 * numX * numY else { return nil } 15 16 let colours: [(Float, Float, Float)] = (0 ..< numX * numY).map { i in 17 if i == 0 { 18 let value = String(blurHash[2 ..< 6]).decode83() 19 return decodeDC(value) 20 } else { 21 let value = String(blurHash[4 + i * 2 ..< 4 + i * 2 + 2]).decode83() 22 return decodeAC(value, maximumValue: maximumValue * punch) 23 } 24 } 25 26 let width = Int(size.width) 27 let height = Int(size.height) 28 let bytesPerRow = width * 3 29 guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { return nil } 30 CFDataSetLength(data, bytesPerRow * height) 31 guard let pixels = CFDataGetMutableBytePtr(data) else { return nil } 32 33 for y in 0 ..< height { 34 for x in 0 ..< width { 35 var r: Float = 0 36 var g: Float = 0 37 var b: Float = 0 38 39 for j in 0 ..< numY { 40 for i in 0 ..< numX { 41 let basis = cos(Float.pi * Float(x) * Float(i) / Float(width)) * cos(Float.pi * Float(y) * Float(j) / Float(height)) 42 let colour = colours[i + j * numX] 43 r += colour.0 * basis 44 g += colour.1 * basis 45 b += colour.2 * basis 46 } 47 } 48 49 let intR = UInt8(linearTosRGB(r)) 50 let intG = UInt8(linearTosRGB(g)) 51 let intB = UInt8(linearTosRGB(b)) 52 53 pixels[3 * x + 0 + y * bytesPerRow] = intR 54 pixels[3 * x + 1 + y * bytesPerRow] = intG 55 pixels[3 * x + 2 + y * bytesPerRow] = intB 56 } 57 } 58 59 let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue) 60 61 guard let provider = CGDataProvider(data: data) else { return nil } 62 guard let cgImage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 24, bytesPerRow: bytesPerRow, 63 space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) else { return nil } 64 65 self.init(cgImage: cgImage) 66 } 67 } 68 69 private func decodeDC(_ value: Int) -> (Float, Float, Float) { 70 let intR = value >> 16 71 let intG = (value >> 8) & 255 72 let intB = value & 255 73 return (sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB)) 74 } 75 76 private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) { 77 let quantR = value / (19 * 19) 78 let quantG = (value / 19) % 19 79 let quantB = value % 19 80 81 let rgb = ( 82 signPow((Float(quantR) - 9) / 9, 2) * maximumValue, 83 signPow((Float(quantG) - 9) / 9, 2) * maximumValue, 84 signPow((Float(quantB) - 9) / 9, 2) * maximumValue 85 ) 86 87 return rgb 88 } 89 90 private func signPow(_ value: Float, _ exp: Float) -> Float { 91 return copysign(pow(abs(value), exp), value) 92 } 93 94 private func linearTosRGB(_ value: Float) -> Int { 95 let v = max(0, min(1, value)) 96 if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) } 97 else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) } 98 } 99 100 private func sRGBToLinear<Type: BinaryInteger>(_ value: Type) -> Float { 101 let v = Float(Int64(value)) / 255 102 if v <= 0.04045 { return v / 12.92 } 103 else { return pow((v + 0.055) / 1.055, 2.4) } 104 } 105 106 private let encodeCharacters: [String] = { 107 return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) } 108 }() 109 110 private let decodeCharacters: [String: Int] = { 111 var dict: [String: Int] = [:] 112 for (index, character) in encodeCharacters.enumerated() { 113 dict[character] = index 114 } 115 return dict 116 }() 117 118 extension String { 119 func decode83() -> Int { 120 var value: Int = 0 121 for character in self { 122 if let digit = decodeCharacters[String(character)] { 123 value = value * 83 + digit 124 } 125 } 126 return value 127 } 128 } 129 130 private extension String { 131 subscript (offset: Int) -> Character { 132 return self[index(startIndex, offsetBy: offset)] 133 } 134 135 subscript (bounds: CountableClosedRange<Int>) -> Substring { 136 let start = index(startIndex, offsetBy: bounds.lowerBound) 137 let end = index(startIndex, offsetBy: bounds.upperBound) 138 return self[start...end] 139 } 140 141 subscript (bounds: CountableRange<Int>) -> Substring { 142 let start = index(startIndex, offsetBy: bounds.lowerBound) 143 let end = index(startIndex, offsetBy: bounds.upperBound) 144 return self[start..<end] 145 } 146 }