Compare commits
5 Commits
master
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01ee49f68d | ||
|
|
822b405e43 | ||
|
|
50c8c73822 | ||
|
|
41445e7ea0 | ||
|
|
a719b2849c |
@ -25,6 +25,7 @@
|
||||
1B49CD271EC47243006F8E7D /* Encode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B49CD261EC47243006F8E7D /* Encode.swift */; };
|
||||
1B49CD291EC4724C006F8E7D /* Decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B49CD281EC4724C006F8E7D /* Decode.swift */; };
|
||||
1B787C901EC4A29C00F167D3 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B787C8F1EC4A29C00F167D3 /* StringExtension.swift */; };
|
||||
1BED97AA1F75488E00255036 /* pic6.png in Resources */ = {isa = PBXBuildFile; fileRef = 1BED97A91F75486400255036 /* pic6.png */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -90,6 +91,7 @@
|
||||
1B49CD261EC47243006F8E7D /* Encode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Encode.swift; sourceTree = "<group>"; };
|
||||
1B49CD281EC4724C006F8E7D /* Decode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Decode.swift; sourceTree = "<group>"; };
|
||||
1B787C8F1EC4A29C00F167D3 /* StringExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = "<group>"; };
|
||||
1BED97A91F75486400255036 /* pic6.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = pic6.png; path = ../../../Downloads/pic6.png; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -140,6 +142,7 @@
|
||||
1B0A25261EC5EAB000F25F08 /* pic3.png */,
|
||||
1B0A25271EC5EAB000F25F08 /* pic4.png */,
|
||||
1B0A25281EC5EAB000F25F08 /* pic5.png */,
|
||||
1BED97A91F75486400255036 /* pic6.png */,
|
||||
);
|
||||
name = Images;
|
||||
sourceTree = "<group>";
|
||||
@ -335,6 +338,7 @@
|
||||
files = (
|
||||
1B0A25291EC5EAB000F25F08 /* pic1.png in Resources */,
|
||||
1B0A252A1EC5EAB000F25F08 /* pic2.png in Resources */,
|
||||
1BED97AA1F75488E00255036 /* pic6.png in Resources */,
|
||||
1B0A252B1EC5EAB000F25F08 /* pic3.png in Resources */,
|
||||
1B0A252C1EC5EAB000F25F08 /* pic4.png in Resources */,
|
||||
1B0A252D1EC5EAB000F25F08 /* pic5.png in Resources */,
|
||||
@ -627,6 +631,7 @@
|
||||
1B2BA1C31F0E5EC6006057C1 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
1B49CD151EC4721A006F8E7D /* Build configuration list for PBXProject "BlurHash" */ = {
|
||||
isa = XCConfigurationList;
|
||||
|
||||
@ -1,15 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12120" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13196" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13173"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<customFonts key="customFonts">
|
||||
<array key="Courier.ttc">
|
||||
<string>Courier</string>
|
||||
</array>
|
||||
</customFonts>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
@ -23,7 +28,7 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" placeholderIntrinsicWidth="240" placeholderIntrinsicHeight="128" translatesAutoresizingMaskIntoConstraints="NO" id="P5Q-KT-MGh">
|
||||
<imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" placeholderIntrinsicWidth="240" placeholderIntrinsicHeight="128" translatesAutoresizingMaskIntoConstraints="NO" id="P5Q-KT-MGh">
|
||||
<rect key="frame" x="67" y="52" width="240" height="128"/>
|
||||
<gestureRecognizers/>
|
||||
<connections>
|
||||
|
||||
@ -9,6 +9,7 @@ class ViewController: UIViewController {
|
||||
@IBOutlet weak var yComponentsLabel: UILabel?
|
||||
|
||||
let images: [UIImage] = [
|
||||
UIImage(named: "pic6.png")!,
|
||||
UIImage(named: "pic2.png")!,
|
||||
UIImage(named: "pic1.png")!,
|
||||
UIImage(named: "pic3.png")!,
|
||||
@ -34,7 +35,7 @@ class ViewController: UIViewController {
|
||||
}
|
||||
|
||||
@IBAction func xPlusTapped() {
|
||||
if xComponents < 8 {
|
||||
if xComponents < 10 {
|
||||
xComponents += 1
|
||||
updateEncode()
|
||||
updateDecode()
|
||||
@ -50,7 +51,7 @@ class ViewController: UIViewController {
|
||||
}
|
||||
|
||||
@IBAction func yPlusTapped() {
|
||||
if yComponents < 8 {
|
||||
if yComponents < 5 {
|
||||
yComponents += 1
|
||||
updateEncode()
|
||||
updateDecode()
|
||||
@ -80,7 +81,11 @@ class ViewController: UIViewController {
|
||||
}
|
||||
|
||||
func updateDecode() {
|
||||
let blurImage = UIImage(blurHash: blurHash, size: CGSize(width: 32, height: 32), punch: punch)
|
||||
guard let image = originalImageView?.image else { return }
|
||||
let blurImage = UIImage(blurHash: blurHash, size: CGSize(
|
||||
width: Int(ceil(32 * sqrt(image.size.width / image.size.height))),
|
||||
height: Int(ceil(32 * sqrt(image.size.height / image.size.width)))),
|
||||
punch: punch)
|
||||
|
||||
blurImageView?.image = blurImage
|
||||
}
|
||||
|
||||
@ -6,15 +6,15 @@ extension UIImage {
|
||||
guard string.length >= 6 else { return nil }
|
||||
|
||||
let sizeFlag = string.substring(with: NSRange(location: 0, length: 1)).decode64()
|
||||
let numY = (sizeFlag >> 3) + 1
|
||||
let numX = (sizeFlag & 7) + 1
|
||||
let numY = (sizeFlag / 10) + 1
|
||||
let numX = (sizeFlag % 10) + 1
|
||||
|
||||
let quantisedMaximumValue = string.substring(with: NSRange(location: 1, length: 1)).decode64()
|
||||
let maximumValue = Float(quantisedMaximumValue + 1) / 128
|
||||
|
||||
guard string.length == 4 + 2 * numX * numY else { return nil }
|
||||
|
||||
let colours: [(Float, Float, Float)] = (0 ..< numX * numY).map { i in
|
||||
var colours: [(Float, Float, Float)] = (0 ..< numX * numY).map { i in
|
||||
if i == 0 {
|
||||
let value = string.substring(with: NSRange(location: 2, length: 4)).decode64()
|
||||
return decodeDC(value)
|
||||
@ -28,6 +28,7 @@ extension UIImage {
|
||||
let height = Int(size.height)
|
||||
let bytesPerRow = width * 3
|
||||
guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { return nil }
|
||||
CFDataSetLength(data, bytesPerRow * height)
|
||||
guard let pixels = CFDataGetMutableBytePtr(data) else { return nil }
|
||||
|
||||
for y in 0 ..< height {
|
||||
@ -36,10 +37,19 @@ extension UIImage {
|
||||
var g: Float = 0
|
||||
var b: Float = 0
|
||||
|
||||
for j in 0 ..< numY {
|
||||
for i in 0 ..< numX {
|
||||
let basis = cos(Float.pi * Float(x) * Float(i) / Float(width)) * cos(Float.pi * Float(y) * Float(j) / Float(height))
|
||||
let colour = colours[i + j * numX]
|
||||
for q in 0 ..< numY {
|
||||
for k in 0 ..< numX {
|
||||
let fx = Double(x) - Double(width) / 2
|
||||
let fy = Double(y) - Double(height) / 2
|
||||
let rr = sqrt(fx * fx + fy * fy) / (Double(width) / 2)
|
||||
let omega = atan2(fy, fx)
|
||||
let K = (k + 1) / 2
|
||||
let Rkq = UIImage.Rqk[q][K]
|
||||
let isCosine = k % 2 == 0
|
||||
//let normalisation = Rkq == 0 ? 1 : sqrt(Double.pi)
|
||||
let basis = Float(/*normalisation * */jn(K, Rkq * rr) * (isCosine ? cos(omega * Double(K)) : sin(omega * Double(K))))
|
||||
|
||||
let colour = colours[k + q * numX]
|
||||
r += colour.0 * basis
|
||||
g += colour.1 * basis
|
||||
b += colour.2 * basis
|
||||
@ -79,9 +89,9 @@ func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) {
|
||||
let quantB = value & 15
|
||||
|
||||
let rgb = (
|
||||
signPow((Float(quantR) - 8) / 8, 3.0) * maximumValue * 2,
|
||||
signPow((Float(quantG) - 8) / 8, 3.0) * maximumValue * 2,
|
||||
signPow((Float(quantB) - 8) / 8, 3.0) * maximumValue * 2
|
||||
signPow((Float(quantR) - 8) / 7, 3.0) * maximumValue * 2,
|
||||
signPow((Float(quantG) - 8) / 7, 3.0) * maximumValue * 2,
|
||||
signPow((Float(quantB) - 8) / 7, 3.0) * maximumValue * 2
|
||||
)
|
||||
|
||||
return rgb
|
||||
|
||||
@ -1,9 +1,29 @@
|
||||
import UIKit
|
||||
|
||||
extension UIImage {
|
||||
static let Rqk: [[Double]] = [
|
||||
// TODO:
|
||||
// http://wwwal.kuicr.kyoto-u.ac.jp/www/accelerator/a4/besselroot.htmlx
|
||||
// http://www.math.usm.edu/lambers/mat415/lecture15.pdf
|
||||
|
||||
[0, 1.8412, 3.0542, 4.2012, 5.3175, 6.4156],
|
||||
[3.8317, 5.3314, 6.7061, 8.0152, 9.2824, 10.5199],
|
||||
[7.0156, 8.5363, 9.9695, 11.3459, 12.6819, 13.9872],
|
||||
[10.1735, 11.7060, 13.1704, 14.5858, 15.9641, 17.3128],
|
||||
[13.3237, 14.8636, 16.3475, 17.7887, 19.1960, 20.5755],
|
||||
|
||||
/* [2.4048, 3.8317, 5.1356, 6.3802, 7.5883, 8.7715],
|
||||
[5.5201, 7.0156, 8.4172, 9.7610, 11.0647, 12.3386],
|
||||
[8.6537, 10.1735, 11.6198, 13.0152, 14.3725, 15.7002],
|
||||
[11.7915, 13.3237, 14.7960, 16.2235, 17.6160, 18.9801],
|
||||
[14.9309, 16.4706, 17.9598, 19.4094, 20.8269, 22.2178],*/
|
||||
]
|
||||
|
||||
// https://lmb.informatik.uni-freiburg.de/Publications/2008/WRB08/wa_report01_08.pdf
|
||||
|
||||
public func blurHash(components: (Int, Int)) -> String? {
|
||||
guard components.0 >= 1, components.0 <= 8,
|
||||
components.1 >= 1, components.1 <= 8,
|
||||
guard components.0 >= 1, components.0 <= 10,
|
||||
components.1 >= 1, components.1 <= 5,
|
||||
cgImage?.colorSpace?.numberOfComponents == 3,
|
||||
cgImage?.bitsPerPixel == 24 || cgImage?.bitsPerPixel == 32 else { return nil }
|
||||
|
||||
@ -17,12 +37,24 @@ extension UIImage {
|
||||
let bytesPerRow = cgImage.bytesPerRow
|
||||
|
||||
var factors: [(Float, Float, Float)] = []
|
||||
for y in 0 ..< components.1 {
|
||||
for x in 0 ..< components.0 {
|
||||
|
||||
for q in 0 ..< components.1 {
|
||||
for k in 0 ..< components.0 {
|
||||
let factor = multiplyBasisFunction(pixels: pixels, width: width, height: height, bytesPerRow: bytesPerRow, bytesPerPixel: cgImage.bitsPerPixel / 8, pixelOffset: 0) {
|
||||
cos(Float.pi * Float(x) * $0 / Float(width)) * cos(Float.pi * Float(y) * $1 / Float(height))
|
||||
let fx = Double($0) - Double(width) / 2
|
||||
let fy = Double($1) - Double(height) / 2
|
||||
let r = sqrt(fx * fx + fy * fy) / (Double(width) / 2)
|
||||
let omega = atan2(fy, fx)
|
||||
guard r < 1 else { return 0 }
|
||||
let K = (k + 1) / 2
|
||||
let Rkq = UIImage.Rqk[q][K]
|
||||
let isCosine = k % 2 == 0
|
||||
return Float(/**/jn(K, Rkq * r) * (isCosine ? cos(omega * Double(K)) : sin(omega * Double(K))))
|
||||
}
|
||||
factors.append(factor)
|
||||
let normalisation: Float = q == 0 && k == 0 ? 1 : 0.5
|
||||
let normalisedFactor = (factor.0 * normalisation, factor.1 * normalisation, factor.2 * normalisation)
|
||||
print("\(q) \(k): \(normalisedFactor)")
|
||||
factors.append(normalisedFactor)
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,12 +63,12 @@ extension UIImage {
|
||||
|
||||
var hash = ""
|
||||
|
||||
let sizeFlag = (components.0 - 1) + ((components.1 - 1) << 3)
|
||||
let sizeFlag = (components.0 - 1) + ((components.1 - 1) * 10)
|
||||
hash += sizeFlag.encode64(length: 1)
|
||||
|
||||
let maximumValue: Float
|
||||
if ac.count > 0 {
|
||||
let actualMaximumValue = ac.map({ max($0.0, $0.1, $0.2) }).max()!
|
||||
let actualMaximumValue = ac.flatMap({ [abs($0.0), abs($0.1), abs($0.2)] }).max()!
|
||||
let quantisedMaximumValue = Int(max(0, min(63, floor(actualMaximumValue * 128 - 0.5))))
|
||||
maximumValue = Float(quantisedMaximumValue + 1) / 128
|
||||
hash += quantisedMaximumValue.encode64(length: 1)
|
||||
@ -60,18 +92,18 @@ extension UIImage {
|
||||
var b: Float = 0
|
||||
|
||||
let buffer = UnsafeBufferPointer(start: pixels, count: height * bytesPerRow)
|
||||
var scale: Float = 0
|
||||
|
||||
for x in 0 ..< width {
|
||||
for y in 0 ..< height {
|
||||
let basis = basisFunction(Float(x), Float(y))
|
||||
r += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 0 + y * bytesPerRow])
|
||||
g += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 1 + y * bytesPerRow])
|
||||
b += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 2 + y * bytesPerRow])
|
||||
r += basis * (sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 0 + y * bytesPerRow]))
|
||||
g += basis * (sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 1 + y * bytesPerRow]))
|
||||
b += basis * (sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 2 + y * bytesPerRow]))
|
||||
scale += basis * basis
|
||||
}
|
||||
}
|
||||
|
||||
let scale = Float(width * height)
|
||||
|
||||
return (r / scale, g / scale, b / scale)
|
||||
}
|
||||
}
|
||||
@ -84,9 +116,9 @@ func encodeDC(_ value: (Float, Float, Float)) -> Int {
|
||||
}
|
||||
|
||||
func encodeAC(_ value: (Float, Float, Float), maximumValue: Float) -> Int {
|
||||
let quantR = Int(max(0, min(15, floor(signPow(value.0 / maximumValue, 0.333) * 8 + 8.5))))
|
||||
let quantG = Int(max(0, min(15, floor(signPow(value.1 / maximumValue, 0.333) * 8 + 8.5))))
|
||||
let quantB = Int(max(0, min(15, floor(signPow(value.2 / maximumValue, 0.333) * 8 + 8.5))))
|
||||
let quantR = Int(max(0, min(15, floor(signPow(value.0 / maximumValue, 0.333) * 7 + 8.5))))
|
||||
let quantG = Int(max(0, min(15, floor(signPow(value.1 / maximumValue, 0.333) * 7 + 8.5))))
|
||||
let quantB = Int(max(0, min(15, floor(signPow(value.2 / maximumValue, 0.333) * 7 + 8.5))))
|
||||
|
||||
return (quantR << 8) + (quantG << 4) + quantB
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user