damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

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 }