BlurHashEncode.swift (5058B)
1 import UIKit 2 3 extension UIImage { 4 public func blurHash(numberOfComponents components: (Int, Int)) -> String? { 5 let pixelWidth = Int(round(size.width * scale)) 6 let pixelHeight = Int(round(size.height * scale)) 7 8 let context = CGContext( 9 data: nil, 10 width: pixelWidth, 11 height: pixelHeight, 12 bitsPerComponent: 8, 13 bytesPerRow: pixelWidth * 4, 14 space: CGColorSpace(name: CGColorSpace.sRGB)!, 15 bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue 16 )! 17 context.scaleBy(x: scale, y: -scale) 18 context.translateBy(x: 0, y: -size.height) 19 20 UIGraphicsPushContext(context) 21 draw(at: .zero) 22 UIGraphicsPopContext() 23 24 guard let cgImage = context.makeImage(), 25 let dataProvider = cgImage.dataProvider, 26 let data = dataProvider.data, 27 let pixels = CFDataGetBytePtr(data) else { 28 assertionFailure("Unexpected error!") 29 return nil 30 } 31 32 let width = cgImage.width 33 let height = cgImage.height 34 let bytesPerRow = cgImage.bytesPerRow 35 36 var factors: [(Float, Float, Float)] = [] 37 for y in 0 ..< components.1 { 38 for x in 0 ..< components.0 { 39 let normalisation: Float = (x == 0 && y == 0) ? 1 : 2 40 let factor = multiplyBasisFunction(pixels: pixels, width: width, height: height, bytesPerRow: bytesPerRow, bytesPerPixel: cgImage.bitsPerPixel / 8, pixelOffset: 0) { 41 normalisation * cos(Float.pi * Float(x) * $0 / Float(width)) as Float * cos(Float.pi * Float(y) * $1 / Float(height)) as Float 42 } 43 factors.append(factor) 44 } 45 } 46 47 let dc = factors.first! 48 let ac = factors.dropFirst() 49 50 var hash = "" 51 52 let sizeFlag = (components.0 - 1) + (components.1 - 1) * 9 53 hash += sizeFlag.encode83(length: 1) 54 55 let maximumValue: Float 56 if ac.count > 0 { 57 let actualMaximumValue = ac.map({ max(abs($0.0), abs($0.1), abs($0.2)) }).max()! 58 let quantisedMaximumValue = Int(max(0, min(82, floor(actualMaximumValue * 166 - 0.5)))) 59 maximumValue = Float(quantisedMaximumValue + 1) / 166 60 hash += quantisedMaximumValue.encode83(length: 1) 61 } else { 62 maximumValue = 1 63 hash += 0.encode83(length: 1) 64 } 65 66 hash += encodeDC(dc).encode83(length: 4) 67 68 for factor in ac { 69 hash += encodeAC(factor, maximumValue: maximumValue).encode83(length: 2) 70 } 71 72 return hash 73 } 74 75 private func multiplyBasisFunction(pixels: UnsafePointer<UInt8>, width: Int, height: Int, bytesPerRow: Int, bytesPerPixel: Int, pixelOffset: Int, basisFunction: (Float, Float) -> Float) -> (Float, Float, Float) { 76 var r: Float = 0 77 var g: Float = 0 78 var b: Float = 0 79 80 let buffer = UnsafeBufferPointer(start: pixels, count: height * bytesPerRow) 81 82 for x in 0 ..< width { 83 for y in 0 ..< height { 84 let basis = basisFunction(Float(x), Float(y)) 85 r += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 0 + y * bytesPerRow]) 86 g += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 1 + y * bytesPerRow]) 87 b += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 2 + y * bytesPerRow]) 88 } 89 } 90 91 let scale = 1 / Float(width * height) 92 93 return (r * scale, g * scale, b * scale) 94 } 95 } 96 97 private func encodeDC(_ value: (Float, Float, Float)) -> Int { 98 let roundedR = linearTosRGB(value.0) 99 let roundedG = linearTosRGB(value.1) 100 let roundedB = linearTosRGB(value.2) 101 return (roundedR << 16) + (roundedG << 8) + roundedB 102 } 103 104 private func encodeAC(_ value: (Float, Float, Float), maximumValue: Float) -> Int { 105 let quantR = Int(max(0, min(18, floor(signPow(value.0 / maximumValue, 0.5) * 9 + 9.5)))) 106 let quantG = Int(max(0, min(18, floor(signPow(value.1 / maximumValue, 0.5) * 9 + 9.5)))) 107 let quantB = Int(max(0, min(18, floor(signPow(value.2 / maximumValue, 0.5) * 9 + 9.5)))) 108 109 return quantR * 19 * 19 + quantG * 19 + quantB 110 } 111 112 private func signPow(_ value: Float, _ exp: Float) -> Float { 113 return copysign(pow(abs(value), exp), value) 114 } 115 116 private func linearTosRGB(_ value: Float) -> Int { 117 let v = max(0, min(1, value)) 118 if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) } 119 else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) } 120 } 121 122 private func sRGBToLinear<Type: BinaryInteger>(_ value: Type) -> Float { 123 let v = Float(Int64(value)) / 255 124 if v <= 0.04045 { return v / 12.92 } 125 else { return pow((v + 0.055) / 1.055, 2.4) } 126 } 127 128 private let encodeCharacters: [String] = { 129 return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) } 130 }() 131 132 extension BinaryInteger { 133 func encode83(length: Int) -> String { 134 var result = "" 135 for i in 1 ... length { 136 let digit = (Int(self) / pow(83, length - i)) % 83 137 result += encodeCharacters[Int(digit)] 138 } 139 return result 140 } 141 } 142 143 private func pow(_ base: Int, _ exponent: Int) -> Int { 144 return (0 ..< exponent).reduce(1) { value, _ in value * base } 145 }