damus

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

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 }