├── TypeScript ├── .gitignore ├── .npmignore ├── .prettierrc ├── src │ ├── index.ts │ ├── error.ts │ ├── utils.ts │ ├── base83.ts │ ├── demo.ts │ ├── decode.ts │ └── encode.ts ├── CHANGELOG.md ├── tsconfig.json ├── webpack.config.js ├── package.json ├── demo │ └── index.html └── README.md ├── Kotlin ├── settings.gradle ├── Readme.md ├── lib │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── wolt │ │ │ └── blurhashkt │ │ │ └── BlurHashDecoder.kt │ ├── build.gradle │ └── proguard-rules.pro ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── demo │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── drawable │ │ │ │ ├── bg_blue_rounded_rect_8.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ └── activity_main.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── wolt │ │ │ └── blurhashapp │ │ │ └── MainActivity.kt │ ├── build.gradle │ └── proguard-rules.pro ├── build.gradle ├── .gitignore ├── gradle.properties ├── gradlew.bat └── gradlew ├── Media ├── HowItWorks1.jpg ├── HowItWorks2.jpg ├── WhyBlurHash.png ├── BadScreenshot.png ├── GoodScreenshot.png └── WhyBlurHash.afphoto ├── Swift ├── BlurHashTest │ ├── pic1.png │ ├── pic2.png │ ├── pic3.png │ ├── pic4.png │ ├── pic5.png │ ├── pic6.jpg │ ├── pic6.png │ ├── AppDelegate.swift │ ├── Info.plist │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── SimpleViewController.swift │ ├── AdvancedViewController.swift │ └── GeneratedViewController.swift ├── BlurHash.xcodeproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── BlurHash.xcscmblueprint ├── BlurHashKit │ ├── ColourSpace.swift │ ├── Info.plist │ ├── StringCoding.swift │ ├── EscapeSequences.swift │ ├── ToString.swift │ ├── TupleMaths.swift │ ├── Generation.swift │ ├── BlurHash.swift │ ├── FromUIImage.swift │ ├── FromString.swift │ ├── ToUIImage.swift │ └── ColourProbes.swift ├── License.txt ├── Readme.md ├── BlurHashEncode.swift └── BlurHashDecode.swift ├── Website ├── assets │ ├── images │ │ ├── img1.jpg │ │ ├── img2.jpg │ │ ├── img3.jpg │ │ ├── img4.jpg │ │ ├── img5.jpg │ │ ├── Bad_screen@2x.png │ │ ├── Good_screen@2x.png │ │ ├── get-started-bg.jpg │ │ ├── iPhone-X-Silver.png │ │ └── arrow.svg │ ├── fonts │ │ └── averta │ │ │ ├── 346526_0_0.eot │ │ │ ├── 346526_0_0.ttf │ │ │ ├── 346526_1_0.eot │ │ │ ├── 346526_1_0.ttf │ │ │ ├── 346526_2_0.eot │ │ │ ├── 346526_2_0.ttf │ │ │ ├── 346526_3_0.eot │ │ │ ├── 346526_3_0.ttf │ │ │ ├── 346526_4_0.eot │ │ │ ├── 346526_4_0.ttf │ │ │ ├── 346526_5_0.eot │ │ │ ├── 346526_5_0.ttf │ │ │ ├── 346526_6_0.eot │ │ │ ├── 346526_6_0.ttf │ │ │ ├── 346526_7_0.eot │ │ │ ├── 346526_7_0.ttf │ │ │ ├── 346526_8_0.eot │ │ │ ├── 346526_8_0.ttf │ │ │ ├── 346526_9_0.eot │ │ │ ├── 346526_9_0.ttf │ │ │ ├── 346526_A_0.eot │ │ │ ├── 346526_A_0.ttf │ │ │ ├── 346526_B_0.eot │ │ │ ├── 346526_B_0.ttf │ │ │ ├── 346526_C_0.eot │ │ │ ├── 346526_C_0.ttf │ │ │ ├── 346526_D_0.eot │ │ │ ├── 346526_D_0.ttf │ │ │ ├── 346526_E_0.eot │ │ │ ├── 346526_E_0.ttf │ │ │ ├── 346526_F_0.eot │ │ │ ├── 346526_F_0.ttf │ │ │ ├── 346526_0_0.woff │ │ │ ├── 346526_0_0.woff2 │ │ │ ├── 346526_1_0.woff │ │ │ ├── 346526_1_0.woff2 │ │ │ ├── 346526_2_0.woff │ │ │ ├── 346526_2_0.woff2 │ │ │ ├── 346526_3_0.woff │ │ │ ├── 346526_3_0.woff2 │ │ │ ├── 346526_4_0.woff │ │ │ ├── 346526_4_0.woff2 │ │ │ ├── 346526_5_0.woff │ │ │ ├── 346526_5_0.woff2 │ │ │ ├── 346526_6_0.woff │ │ │ ├── 346526_6_0.woff2 │ │ │ ├── 346526_7_0.woff │ │ │ ├── 346526_7_0.woff2 │ │ │ ├── 346526_8_0.woff │ │ │ ├── 346526_8_0.woff2 │ │ │ ├── 346526_9_0.woff │ │ │ ├── 346526_9_0.woff2 │ │ │ ├── 346526_A_0.woff │ │ │ ├── 346526_A_0.woff2 │ │ │ ├── 346526_B_0.woff │ │ │ ├── 346526_B_0.woff2 │ │ │ ├── 346526_C_0.woff │ │ │ ├── 346526_C_0.woff2 │ │ │ ├── 346526_D_0.woff │ │ │ ├── 346526_D_0.woff2 │ │ │ ├── 346526_E_0.woff │ │ │ ├── 346526_E_0.woff2 │ │ │ ├── 346526_F_0.woff │ │ │ └── 346526_F_0.woff2 │ └── svg │ │ └── wolt-logo.svg ├── src │ ├── index.js │ ├── constants.js │ ├── styles │ │ ├── variables.scss │ │ ├── utils.scss │ │ ├── base.scss │ │ ├── resets.scss │ │ └── averta.scss │ ├── sections │ │ ├── hero.js │ │ └── demo.js │ ├── index.ejs │ └── index.scss ├── .babelrc ├── Readme.md ├── .prettierrc ├── package.json ├── webpack.config.js └── deploy.sh ├── .gitmodules ├── C ├── Makefile ├── encode.h ├── blurhash_stb.c ├── Readme.md └── encode.c ├── .gitignore ├── CodeOfConduct.md └── Algorithm.md /TypeScript/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ -------------------------------------------------------------------------------- /Kotlin/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':demo', ':lib' 2 | -------------------------------------------------------------------------------- /TypeScript/.npmignore: -------------------------------------------------------------------------------- 1 | tsconfig.json 2 | src 3 | demo 4 | -------------------------------------------------------------------------------- /Kotlin/Readme.md: -------------------------------------------------------------------------------- 1 | # BlurHash in Kotlin, for Android 2 | 3 | 4 | -------------------------------------------------------------------------------- /Kotlin/lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Media/HowItWorks1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Media/HowItWorks1.jpg -------------------------------------------------------------------------------- /Media/HowItWorks2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Media/HowItWorks2.jpg -------------------------------------------------------------------------------- /Media/WhyBlurHash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Media/WhyBlurHash.png -------------------------------------------------------------------------------- /Media/BadScreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Media/BadScreenshot.png -------------------------------------------------------------------------------- /Media/GoodScreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Media/GoodScreenshot.png -------------------------------------------------------------------------------- /Media/WhyBlurHash.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Media/WhyBlurHash.afphoto -------------------------------------------------------------------------------- /Swift/BlurHashTest/pic1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Swift/BlurHashTest/pic1.png -------------------------------------------------------------------------------- /Swift/BlurHashTest/pic2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Swift/BlurHashTest/pic2.png -------------------------------------------------------------------------------- /Swift/BlurHashTest/pic3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Swift/BlurHashTest/pic3.png -------------------------------------------------------------------------------- /Swift/BlurHashTest/pic4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Swift/BlurHashTest/pic4.png -------------------------------------------------------------------------------- /Swift/BlurHashTest/pic5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Swift/BlurHashTest/pic5.png -------------------------------------------------------------------------------- /Swift/BlurHashTest/pic6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Swift/BlurHashTest/pic6.jpg -------------------------------------------------------------------------------- /Swift/BlurHashTest/pic6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Swift/BlurHashTest/pic6.png -------------------------------------------------------------------------------- /Website/assets/images/img1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/images/img1.jpg -------------------------------------------------------------------------------- /Website/assets/images/img2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/images/img2.jpg -------------------------------------------------------------------------------- /Website/assets/images/img3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/images/img3.jpg -------------------------------------------------------------------------------- /Website/assets/images/img4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/images/img4.jpg -------------------------------------------------------------------------------- /Website/assets/images/img5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/images/img5.jpg -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Python"] 2 | path = Python 3 | url = https://github.com/creditornot/blurhash-python.git 4 | -------------------------------------------------------------------------------- /Kotlin/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Kotlin/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Website/assets/images/Bad_screen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/images/Bad_screen@2x.png -------------------------------------------------------------------------------- /Website/assets/images/Good_screen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/images/Good_screen@2x.png -------------------------------------------------------------------------------- /Website/assets/images/get-started-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/images/get-started-bg.jpg -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_0_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_0_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_0_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_0_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_1_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_1_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_1_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_1_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_2_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_2_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_2_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_2_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_3_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_3_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_3_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_3_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_4_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_4_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_4_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_4_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_5_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_5_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_5_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_5_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_6_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_6_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_6_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_6_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_7_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_7_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_7_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_7_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_8_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_8_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_8_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_8_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_9_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_9_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_9_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_9_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_A_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_A_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_A_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_A_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_B_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_B_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_B_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_B_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_C_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_C_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_C_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_C_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_D_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_D_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_D_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_D_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_E_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_E_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_E_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_E_0.ttf -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_F_0.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_F_0.eot -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_F_0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_F_0.ttf -------------------------------------------------------------------------------- /Website/assets/images/iPhone-X-Silver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/images/iPhone-X-Silver.png -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_0_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_0_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_0_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_0_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_1_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_1_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_1_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_1_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_2_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_2_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_2_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_2_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_3_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_3_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_3_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_3_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_4_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_4_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_4_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_4_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_5_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_5_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_5_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_5_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_6_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_6_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_6_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_6_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_7_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_7_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_7_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_7_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_8_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_8_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_8_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_8_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_9_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_9_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_9_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_9_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_A_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_A_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_A_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_A_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_B_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_B_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_B_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_B_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_C_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_C_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_C_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_C_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_D_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_D_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_D_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_D_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_E_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_E_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_E_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_E_0.woff2 -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_F_0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_F_0.woff -------------------------------------------------------------------------------- /Website/assets/fonts/averta/346526_F_0.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Website/assets/fonts/averta/346526_F_0.woff2 -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Kotlin/demo/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Kotlin/demo/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Website/src/index.js: -------------------------------------------------------------------------------- 1 | import './index.scss'; 2 | 3 | import Hero from './sections/hero'; 4 | import Demo from './sections/demo'; 5 | 6 | Hero(); 7 | Demo(); -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Kotlin/demo/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Kotlin/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Kotlin/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Kotlin/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Kotlin/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Kotlin/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Kotlin/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /TypeScript/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "overrides": [ 4 | { 5 | "files": ["*.ts"], 6 | "options": {} 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /TypeScript/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as decode, isBlurhashValid } from "./decode"; 2 | export { default as encode } from "./encode"; 3 | export * from "./error"; 4 | -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasa8x/blurhash/master/Kotlin/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /C/Makefile: -------------------------------------------------------------------------------- 1 | PROGRAM=blurhash 2 | 3 | $(PROGRAM): blurhash_stb.c encode.c encode.h stb_image.h 4 | $(CC) -o $@ blurhash_stb.c encode.c -lm 5 | 6 | .PHONY: clean 7 | clean: 8 | rm -f $(PROGRAM) 9 | -------------------------------------------------------------------------------- /TypeScript/src/error.ts: -------------------------------------------------------------------------------- 1 | export class ValidationError extends Error { 2 | constructor(message: string) { 3 | super(message); 4 | this.name = "ValidationError"; 5 | this.message = message; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Website/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": "> 0.25%, not dead", 7 | "modules": false 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /Website/Readme.md: -------------------------------------------------------------------------------- 1 | # blurha.sh 2 | 3 | Website for blurhash lives here. 4 | 5 | ## Developing 6 | 7 | `npm start` runs the development server 8 | 9 | `npm deploy` builds the site, moves the contents into gh-pages branch and pushes it. -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | BlurHash 3 | BlurHash string 4 | Decode! 5 | 6 | -------------------------------------------------------------------------------- /Swift/BlurHash.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TypeScript/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1.2 (June 29, 2019) 4 | 5 | - added `isBlurhashValid()` utility 6 | 7 | ## 1.1.1 (June 29, 2019) 8 | 9 | - fixed incorrect type declaration path in package.json 10 | - improved error handling 11 | -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #29b6f6 4 | #0086c3 5 | #444444 6 | 7 | 8 | -------------------------------------------------------------------------------- /C/encode.h: -------------------------------------------------------------------------------- 1 | #ifndef __BLURHASH_ENCODE_H__ 2 | #define __BLURHASH_ENCODE_H__ 3 | 4 | #include 5 | #include 6 | 7 | const char *blurHashForPixels(int xComponents, int yComponents, int width, int height, uint8_t *rgb, size_t bytesPerRow); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /Website/src/constants.js: -------------------------------------------------------------------------------- 1 | const imageHashes = [ 2 | 'LEHV6njZ2ykUpyoKadR*.8kCMdnj', 3 | 'LHF5]+c[^6#M@-5b,1J5@[or[kA{', 4 | 'L6Pj0^xZ.A.S_Nt7t7R+*0o}DgQ-', 5 | 'LKO2?U%2Tw=_]~VeVZRi};RPxuwH', 6 | 'LPPGdFog?wt7?HofM|R+OGRjr;xu', 7 | ]; 8 | 9 | export { imageHashes }; 10 | -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/drawable/bg_blue_rounded_rect_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Kotlin/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jul 01 10:02:38 EEST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /TypeScript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "lib": ["es5", "dom"], 6 | "declaration": true, 7 | "outDir": "./dist/", 8 | "sourceMap": true, 9 | "noImplicitAny": true 10 | }, 11 | "include": ["src/index.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Swift/BlurHash.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Swift/BlurHashTest/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | @UIApplicationMain 4 | class AppDelegate: UIResponder, UIApplicationDelegate { 5 | 6 | var window: UIWindow? 7 | 8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 9 | return true 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## OS X specific 2 | .DS_Store 3 | 4 | ## Various settings 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata/ 14 | C/blurhash 15 | Ruby/.* 16 | Ruby/Makefile 17 | Python/build/ 18 | *.bundle 19 | *.so 20 | *.o 21 | *.pyc 22 | 23 | # Website 24 | Website/node_modules/ 25 | Website/dist/ 26 | -------------------------------------------------------------------------------- /Kotlin/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | 4 | ext.kotlin_version = '1.3.50' 5 | 6 | repositories { 7 | google() 8 | jcenter() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.5.0' 13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 14 | } 15 | 16 | } 17 | 18 | allprojects { 19 | 20 | repositories { 21 | google() 22 | jcenter() 23 | } 24 | 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /Swift/BlurHashKit/ColourSpace.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func signPow(_ value: Float, _ exp: Float) -> Float { 4 | return copysign(pow(abs(value), exp), value) 5 | } 6 | 7 | func linearTosRGB(_ value: Float) -> Int { 8 | let v = max(0, min(1, value)) 9 | if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) } 10 | else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) } 11 | } 12 | 13 | func sRGBToLinear(_ value: Type) -> Float { 14 | let v = Float(Int64(value)) / 255 15 | if v <= 0.04045 { return v / 12.92 } 16 | else { return pow((v + 0.055) / 1.055, 2.4) } 17 | } 18 | -------------------------------------------------------------------------------- /TypeScript/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/demo.ts', 5 | devtool: 'inline-source-map', 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.tsx?$/, 10 | use: 'ts-loader', 11 | exclude: /node_modules/ 12 | } 13 | ] 14 | }, 15 | resolve: { 16 | extensions: [ '.tsx', '.ts', '.js' ] 17 | }, 18 | output: { 19 | filename: 'demo.js', 20 | path: path.resolve(__dirname, 'dist') 21 | }, 22 | devServer: { 23 | contentBase: path.join(__dirname, "demo"), 24 | compress: true, 25 | port: 9000 26 | } 27 | }; -------------------------------------------------------------------------------- /Website/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "overrides": [ 4 | { 5 | "files": ["*.js"], 6 | "options": { 7 | "singleQuote": true, 8 | "trailingComma": "all" 9 | } 10 | }, 11 | { 12 | "files": ["webpack.config.js"], 13 | "options": { 14 | "trailingComma": "es5" 15 | } 16 | }, 17 | { 18 | "files": ["*.css", "*.scss"], 19 | "options": { 20 | "parser": "scss", 21 | "singleQuote": true 22 | } 23 | }, 24 | { 25 | "files": "*.json", 26 | "options": { 27 | "parser": "json" 28 | } 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /Website/src/styles/variables.scss: -------------------------------------------------------------------------------- 1 | @function rem($pixels, $context: 16px) { 2 | $pixels: strip-units($pixels); 3 | $context: strip-units($context); 4 | @return ($pixels / $context) * 1rem; 5 | } 6 | 7 | $wolt-id-blue: #0019ff; 8 | $error-color-id: #ff1900; 9 | $header-height: 70px; 10 | // Brand 11 | $wolt-blue: #0065aa; 12 | $wolt-blue-light: #4a90e2; 13 | $wolt-blue-lighter: rgba(74, 144, 226, 0.5); 14 | $wolt-blue-gradient: linear-gradient(-180deg, #0077c8 0%, #0065aa 100%); 15 | 16 | // Text 17 | $offwhite: #eff1f3; 18 | $offwhite-light: #f7f8f9; 19 | $dark-grey: #202125; 20 | $text-color: #404040; 21 | $text-grey: #838383; 22 | $text-grey-light: #acacac; 23 | -------------------------------------------------------------------------------- /TypeScript/src/utils.ts: -------------------------------------------------------------------------------- 1 | export const sRGBToLinear = (value: number) => { 2 | let v = value / 255; 3 | if (v <= 0.04045) { 4 | return v / 12.92; 5 | } else { 6 | return Math.pow((v + 0.055) / 1.055, 2.4); 7 | } 8 | }; 9 | 10 | export const linearTosRGB = (value: number) => { 11 | let v = Math.max(0, Math.min(1, value)); 12 | if (v <= 0.0031308) { 13 | return Math.round(v * 12.92 * 255 + 0.5); 14 | } else { 15 | return Math.round((1.055 * Math.pow(v, 1 / 2.4) - 0.055) * 255 + 0.5); 16 | } 17 | }; 18 | 19 | export const sign = (n: number) => (n < 0 ? -1 : 1); 20 | 21 | export const signPow = (val: number, exp: number) => 22 | sign(val) * Math.pow(Math.abs(val), exp); 23 | -------------------------------------------------------------------------------- /Kotlin/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | .idea/.workspace 29 | 30 | # http://stackoverflow.com/questions/16736856/what-should-be-in-my-gitignore-for-an-android-studio-project 31 | .gradle 32 | /local.properties 33 | /.idea/workspace.xml 34 | /.idea/libraries 35 | .DS_Store 36 | /build 37 | .idea 38 | **/*.iml 39 | *.hprof 40 | **/*.project 41 | -------------------------------------------------------------------------------- /Kotlin/demo/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | 7 | compileSdkVersion 29 8 | 9 | defaultConfig { 10 | applicationId "com.wolt.blurhash" 11 | minSdkVersion 14 12 | targetSdkVersion 29 13 | versionCode 1 14 | versionName "1.0" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | 24 | } 25 | 26 | dependencies { 27 | implementation project(path: ':lib') 28 | implementation 'androidx.appcompat:appcompat:1.1.0' 29 | } 30 | -------------------------------------------------------------------------------- /Kotlin/lib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android-extensions' 3 | apply plugin: 'kotlin-android' 4 | 5 | android { 6 | 7 | compileSdkVersion 29 8 | 9 | defaultConfig { 10 | minSdkVersion 14 11 | targetSdkVersion 29 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation fileTree(dir: 'libs', include: ['*.jar']) 29 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 30 | } 31 | -------------------------------------------------------------------------------- /Kotlin/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx1536m 12 | # When configured, Gradle will run in incubating parallel mode. 13 | # This option should only be used with decoupled projects. More details, visit 14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 15 | # org.gradle.parallel=true 16 | -------------------------------------------------------------------------------- /Kotlin/demo/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /Kotlin/demo/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Kotlin/lib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /Swift/BlurHashKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Kotlin/demo/src/main/java/com/wolt/blurhashapp/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.wolt.blurhashapp 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import android.widget.EditText 6 | import android.widget.ImageView 7 | import android.widget.TextView 8 | import androidx.appcompat.app.AppCompatActivity 9 | import com.wolt.blurhashkt.BlurHashDecoder 10 | 11 | class MainActivity : AppCompatActivity() { 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | setContentView(R.layout.activity_main) 16 | val etInput: EditText = findViewById(R.id.etInput) 17 | val ivResult: ImageView = findViewById(R.id.ivResult) 18 | findViewById(R.id.tvDecode).setOnClickListener { 19 | val bitmap = BlurHashDecoder.decode(etInput.text.toString(), 20, 12) 20 | ivResult.setImageBitmap(bitmap) 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Website/src/styles/utils.scss: -------------------------------------------------------------------------------- 1 | @mixin fluid-type($properties, $min-vw, $max-vw, $min-value, $max-value) { 2 | & { 3 | @each $property in $properties { 4 | #{$property}: $min-value; 5 | } 6 | 7 | @media screen and (min-width: $min-vw) { 8 | @each $property in $properties { 9 | #{$property}: calc( 10 | #{$min-value} + 11 | #{strip-unit($max-value - $min-value)} * 12 | (100vw - #{$min-vw}) / 13 | #{strip-unit($max-vw - $min-vw)} 14 | ); 15 | } 16 | } 17 | 18 | @media screen and (min-width: $max-vw) { 19 | @each $property in $properties { 20 | #{$property}: $max-value; 21 | } 22 | } 23 | } 24 | } 25 | 26 | @function strip-unit($value) { 27 | @return $value / ($value * 0 + 1); 28 | } 29 | 30 | @function top($px) { 31 | @return $px / 768 * 100%; 32 | } 33 | 34 | @function left($px) { 35 | @return $px / 1440 * 100%; 36 | } 37 | -------------------------------------------------------------------------------- /TypeScript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blurhash", 3 | "version": "1.1.3", 4 | "description": "Encoder and decoder for the Wolt BlurHash algorithm.", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/woltapp/blurhash/tree/master/TypeScript" 10 | }, 11 | "homepage": "http://blurhash.com", 12 | "scripts": { 13 | "prepublishOnly": "npm run build", 14 | "build": "npm run ts", 15 | "demo": "webpack-dev-server --mode development", 16 | "prettier": "prettier src/**/*.ts", 17 | "prettier-fix": "npm run prettier -- --write", 18 | "ts": "tsc", 19 | "ts:watch": "npm run ts -- --noEmit --watch" 20 | }, 21 | "keywords": [ 22 | "blurhash", 23 | "blur", 24 | "hash", 25 | "image" 26 | ], 27 | "author": "omahlama", 28 | "license": "MIT", 29 | "devDependencies": { 30 | "prettier": "1.18.2", 31 | "ts-loader": "6.0.4", 32 | "typescript": "3.5.3", 33 | "webpack": "4.38.0", 34 | "webpack-cli": "^3.3.6", 35 | "webpack-dev-server": "3.7.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Swift/BlurHashKit/StringCoding.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | private let encodeCharacters: [String] = { 4 | return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) } 5 | }() 6 | 7 | private let decodeCharacters: [String: Int] = { 8 | var dict: [String: Int] = [:] 9 | for (index, character) in encodeCharacters.enumerated() { 10 | dict[character] = index 11 | } 12 | return dict 13 | }() 14 | 15 | extension BinaryInteger { 16 | func encode83(length: Int) -> String { 17 | var result = "" 18 | for i in 1 ... length { 19 | let digit = (Int(self) / pow(83, length - i)) % 83 20 | result += encodeCharacters[Int(digit)] 21 | } 22 | return result 23 | } 24 | } 25 | 26 | extension String { 27 | func decode83() -> Int { 28 | var value: Int = 0 29 | for character in self { 30 | if let digit = decodeCharacters[String(character)] { 31 | value = value * 83 + digit 32 | } 33 | } 34 | return value 35 | } 36 | } 37 | 38 | private func pow(_ base: Int, _ exponent: Int) -> Int { 39 | return (0 ..< exponent).reduce(1) { value, _ in value * base } 40 | } 41 | -------------------------------------------------------------------------------- /Swift/License.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Wolt Enterprises 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "1.0.0", 4 | "description": "Website for BlurHash promo", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --mode development", 8 | "build": "webpack --mode production", 9 | "clean": "rm -rf dist/*", 10 | "deploy": "npm run clean && npm run build && echo 'blurha.sh' > dist/CNAME && ./deploy.sh" 11 | }, 12 | "author": "serushakov", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@babel/cli": "^7.5.5", 16 | "@babel/core": "^7.5.5", 17 | "@babel/preset-env": "^7.5.5", 18 | "babel-loader": "^8.0.6", 19 | "css-loader": "^3.1.0", 20 | "extract-text-webpack-plugin": "4.0.0-alpha.0", 21 | "file-loader": "^4.1.0", 22 | "html-webpack-plugin": "3.2.0", 23 | "mini-css-extract-plugin": "0.8.0", 24 | "node-sass": "4.12.0", 25 | "prettier": "1.18.2", 26 | "sass-loader": "7.1.0", 27 | "style-loader": "0.23.1", 28 | "webpack": "4.38.0", 29 | "webpack-cli": "3.3.6", 30 | "webpack-dev-server": "3.7.2" 31 | }, 32 | "dependencies": { 33 | "@babel/runtime": "7.5.5", 34 | "smoothscroll-polyfill": "0.4.4", 35 | "velocity-animate": "1.5.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /C/blurhash_stb.c: -------------------------------------------------------------------------------- 1 | #include "encode.h" 2 | 3 | #define STB_IMAGE_IMPLEMENTATION 4 | #include "stb_image.h" 5 | 6 | #include 7 | 8 | const char *blurHashForFile(int xComponents, int yComponents,const char *filename); 9 | 10 | int main(int argc, const char **argv) { 11 | if(argc != 4) { 12 | fprintf(stderr, "Usage: %s x_components y_components imagefile\n", argv[0]); 13 | return 1; 14 | } 15 | 16 | int xComponents = atoi(argv[1]); 17 | int yComponents = atoi(argv[2]); 18 | if(xComponents < 1 || xComponents > 8 || yComponents < 1 || yComponents > 8) { 19 | fprintf(stderr, "Component counts must be between 1 and 8.\n"); 20 | return 1; 21 | } 22 | 23 | const char *hash = blurHashForFile(xComponents, yComponents, argv[3]); 24 | if(!hash) { 25 | fprintf(stderr, "Failed to load image file \"%s\".\n", argv[3]); 26 | return 1; 27 | } 28 | 29 | printf("%s\n", hash); 30 | 31 | return 0; 32 | } 33 | 34 | const char *blurHashForFile(int xComponents, int yComponents,const char *filename) { 35 | int width, height, channels; 36 | unsigned char *data = stbi_load(filename, &width, &height, &channels, 3); 37 | if(!data) return NULL; 38 | 39 | const char *hash = blurHashForPixels(xComponents, yComponents, width, height, data, width * 3); 40 | 41 | stbi_image_free(data); 42 | 43 | return hash; 44 | } 45 | -------------------------------------------------------------------------------- /Website/assets/images/arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Swift/BlurHashTest/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /TypeScript/src/base83.ts: -------------------------------------------------------------------------------- 1 | const digitCharacters = [ 2 | "0", 3 | "1", 4 | "2", 5 | "3", 6 | "4", 7 | "5", 8 | "6", 9 | "7", 10 | "8", 11 | "9", 12 | "A", 13 | "B", 14 | "C", 15 | "D", 16 | "E", 17 | "F", 18 | "G", 19 | "H", 20 | "I", 21 | "J", 22 | "K", 23 | "L", 24 | "M", 25 | "N", 26 | "O", 27 | "P", 28 | "Q", 29 | "R", 30 | "S", 31 | "T", 32 | "U", 33 | "V", 34 | "W", 35 | "X", 36 | "Y", 37 | "Z", 38 | "a", 39 | "b", 40 | "c", 41 | "d", 42 | "e", 43 | "f", 44 | "g", 45 | "h", 46 | "i", 47 | "j", 48 | "k", 49 | "l", 50 | "m", 51 | "n", 52 | "o", 53 | "p", 54 | "q", 55 | "r", 56 | "s", 57 | "t", 58 | "u", 59 | "v", 60 | "w", 61 | "x", 62 | "y", 63 | "z", 64 | "#", 65 | "$", 66 | "%", 67 | "*", 68 | "+", 69 | ",", 70 | "-", 71 | ".", 72 | ":", 73 | ";", 74 | "=", 75 | "?", 76 | "@", 77 | "[", 78 | "]", 79 | "^", 80 | "_", 81 | "{", 82 | "|", 83 | "}", 84 | "~" 85 | ]; 86 | 87 | export const decode83 = (str: String) => { 88 | let value = 0; 89 | for (let i = 0; i < str.length; i++) { 90 | const c = str[i]; 91 | const digit = digitCharacters.indexOf(c); 92 | value = value * 83 + digit; 93 | } 94 | return value; 95 | }; 96 | 97 | export const encode83 = (n: number, length: number): string => { 98 | var result = ""; 99 | for (let i = 1; i <= length; i++) { 100 | let digit = (Math.floor(n) / Math.pow(83, length - i)) % 83; 101 | result += digitCharacters[Math.floor(digit)]; 102 | } 103 | return result; 104 | }; 105 | -------------------------------------------------------------------------------- /Swift/BlurHash.xcodeproj/project.xcworkspace/xcshareddata/BlurHash.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "8D94AD06B7E57997939C2DBED75F327A184CF7B8", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "8D94AD06B7E57997939C2DBED75F327A184CF7B8" : 9223372036854775807 8 | }, 9 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "78AF9E08-7F50-4648-8F5B-F7536E5EB55E", 10 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 11 | "8D94AD06B7E57997939C2DBED75F327A184CF7B8" : "BlurHash\/" 12 | }, 13 | "DVTSourceControlWorkspaceBlueprintNameKey" : "BlurHash", 14 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 15 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "BlurHash.xcodeproj", 16 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 17 | { 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/creditornot\/BlurHash.git", 19 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8D94AD06B7E57997939C2DBED75F327A184CF7B8" 21 | }, 22 | { 23 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/creditornot\/BlurHash.git", 24 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8D94AD06B7E57997939C2DBED75F327A184CF7B8" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /Swift/BlurHashKit/EscapeSequences.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension BlurHash { 4 | var twoByThreeEscapeSequence: String { 5 | let areas: [(from: (Float, Float), to: (Float, Float))] = [ 6 | (from: (0, 0), to: (0.333, 0.5)), 7 | (from: (0, 0.5), to: (0.333, 1.0)), 8 | (from: (0.333, 0), to: (0.666, 0.5)), 9 | (from: (0.333, 0.5), to: (0.666, 1.0)), 10 | (from: (0.666, 0), to: (1.0, 0.5)), 11 | (from: (0.666, 0.5), to: (1.0, 1.0)), 12 | ] 13 | 14 | let rgb: [(Float, Float, Float)] = areas.map { area in 15 | linearRgb(from: area.from, to: area.to) 16 | } 17 | 18 | let maxRgb: (Float, Float, Float) = rgb.reduce((-Float.infinity, -Float.infinity, -Float.infinity), max) 19 | let minRgb: (Float, Float, Float) = rgb.reduce((Float.infinity, Float.infinity, Float.infinity), min) 20 | 21 | let positiveScale: (Float, Float, Float) = ((1, 1, 1) - averageLinearRgb) / (maxRgb - averageLinearRgb) 22 | let negativeScale: (Float, Float, Float) = averageLinearRgb / (averageLinearRgb - minRgb) 23 | let scale: (Float, Float, Float) = min(positiveScale, negativeScale) 24 | 25 | let scaledRgb: [(Float, Float, Float)] = rgb.map { rgb in 26 | return (rgb - averageLinearRgb) * scale + averageLinearRgb 27 | } 28 | 29 | let c = scaledRgb.map { rgb in 30 | return (linearTosRGB(rgb.0) / 51) * 36 + (linearTosRGB(rgb.1) / 51) * 6 + (linearTosRGB(rgb.2) / 51) + 16 31 | } 32 | 33 | return "\u{1b}[38;5;\(c[1]);48;5;\(c[0])m▄\u{1b}[38;5;\(c[3]);48;5;\(c[2])m▄\u{1b}[38;5;\(c[5]);48;5;\(c[4])m▄\u{1b}[m" 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 35 | 36 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Swift/BlurHashKit/ToString.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension BlurHash { 4 | var string: String { 5 | let flatComponents = components.reduce([]) { $0 + $1 } 6 | let dc = flatComponents.first! 7 | let ac = flatComponents.dropFirst() 8 | 9 | var hash = "" 10 | 11 | let sizeFlag = (numberOfHorizontalComponents - 1) + (numberOfVerticalComponents - 1) * 9 12 | hash += sizeFlag.encode83(length: 1) 13 | 14 | let maximumValue: Float 15 | if ac.count > 0 { 16 | let actualMaximumValue = ac.map({ max(abs($0.0), abs($0.1), abs($0.2)) }).max()! 17 | let quantisedMaximumValue = Int(max(0, min(82, floor(actualMaximumValue * 166 - 0.5)))) 18 | maximumValue = Float(quantisedMaximumValue + 1) / 166 19 | hash += quantisedMaximumValue.encode83(length: 1) 20 | } else { 21 | maximumValue = 1 22 | hash += 0.encode83(length: 1) 23 | } 24 | 25 | hash += encodeDC(dc).encode83(length: 4) 26 | 27 | for factor in ac { 28 | hash += encodeAC(factor, maximumValue: maximumValue).encode83(length: 2) 29 | } 30 | 31 | return hash 32 | } 33 | 34 | private func encodeDC(_ value: (Float, Float, Float)) -> Int { 35 | let roundedR = linearTosRGB(value.0) 36 | let roundedG = linearTosRGB(value.1) 37 | let roundedB = linearTosRGB(value.2) 38 | return (roundedR << 16) + (roundedG << 8) + roundedB 39 | } 40 | 41 | private func encodeAC(_ value: (Float, Float, Float), maximumValue: Float) -> Int { 42 | let quantR = Int(max(0, min(18, floor(signPow(value.0 / maximumValue, 0.5) * 9 + 9.5)))) 43 | let quantG = Int(max(0, min(18, floor(signPow(value.1 / maximumValue, 0.5) * 9 + 9.5)))) 44 | let quantB = Int(max(0, min(18, floor(signPow(value.2 / maximumValue, 0.5) * 9 + 9.5)))) 45 | 46 | return quantR * 19 * 19 + quantG * 19 + quantB 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /C/Readme.md: -------------------------------------------------------------------------------- 1 | # BlurHash encoder in portable C 2 | 3 | This code implements an encoder for the BlurHash algorithm in C. It can be used to integrate into other language 4 | using an FFI interface. Currently the Python integration uses this code. 5 | 6 | ## Usage as a library 7 | 8 | Include the `encode.c` and `encode.h` files in your project. They have no external dependencies. 9 | 10 | A single file function is defined: 11 | 12 | const char *blurHashForPixels(int xComponents, int yComponents, int width, int height, uint8_t *rgb, size_t bytesPerRow) { 13 | 14 | This function returns a string containing the BlurHash. This memory is managed by the function, and you should not free it. 15 | It will be overwritten on the next call into the function, so be careful! 16 | 17 | * `xComponents` - The number of components in the X direction. Must be between 1 and 9. 3 to 5 is usually a good range for this. 18 | * `yComponents` - The number of components in the Y direction. Must be between 1 and 9. 3 to 5 is usually a good range for this. 19 | * `width` - The width in pixels of the supplied image. 20 | * `height` - The height in pixels of the supplied image. 21 | * `rgb` - A pointer to the pixel data. This is supplied in RGB order, with 3 bytes per pixels. 22 | * `bytesPerRow` - The number of bytes per row of the RGB pixel data. 23 | 24 | ## Usage as a command-line tool 25 | 26 | You can also build a command-line version to test the encoder. However, note that it uses `stb_image` to load images, 27 | which is not really security-hardened, so it is **not** recommended to use this version in production on untrusted data! 28 | Use one of the integrations instead, which use more robust image loading libraries. 29 | 30 | Nevertheless, if you want to try it out quickly, simply run: 31 | 32 | $ make 33 | $ ./blurhash 4 3 ../Swift/BlurHashTest/pic1.png 34 | LaJHjmVu8_~po#smR+a~xaoLWCRj 35 | -------------------------------------------------------------------------------- /Swift/BlurHashKit/TupleMaths.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func +(lhs: (Float, Float, Float), rhs: (Float, Float, Float)) -> (Float, Float, Float) { 4 | return (lhs.0 + rhs.0, lhs.1 + rhs.1, lhs.2 + rhs.2) 5 | } 6 | 7 | func -(lhs: (Float, Float, Float), rhs: (Float, Float, Float)) -> (Float, Float, Float) { 8 | return (lhs.0 - rhs.0, lhs.1 - rhs.1, lhs.2 - rhs.2) 9 | } 10 | 11 | func *(lhs: (Float, Float, Float), rhs: (Float, Float, Float)) -> (Float, Float, Float) { 12 | return (lhs.0 * rhs.0, lhs.1 * rhs.1, lhs.2 * rhs.2) 13 | } 14 | 15 | func *(lhs: (Float, Float, Float), rhs: Float) -> (Float, Float, Float) { 16 | return (lhs.0 * rhs, lhs.1 * rhs, lhs.2 * rhs) 17 | } 18 | 19 | func *(lhs: Float, rhs: (Float, Float, Float)) -> (Float, Float, Float) { 20 | return (lhs * rhs.0, lhs * rhs.1, lhs * rhs.2) 21 | } 22 | 23 | func /(lhs: (Float, Float, Float), rhs: (Float, Float, Float)) -> (Float, Float, Float) { 24 | return (lhs.0 / rhs.0, lhs.1 / rhs.1, lhs.2 / rhs.2) 25 | } 26 | 27 | func /(lhs: (Float, Float, Float), rhs: Float) -> (Float, Float, Float) { 28 | return (lhs.0 / rhs, lhs.1 / rhs, lhs.2 / rhs) 29 | } 30 | 31 | func +=(lhs: inout (Float, Float, Float), rhs: (Float, Float, Float)) { 32 | lhs = lhs + rhs 33 | } 34 | 35 | func -=(lhs: inout (Float, Float, Float), rhs: (Float, Float, Float)) { 36 | lhs = lhs - rhs 37 | } 38 | 39 | func *=(lhs: inout (Float, Float, Float), rhs: Float) { 40 | lhs = lhs * rhs 41 | } 42 | 43 | func /=(lhs: inout (Float, Float, Float), rhs: Float) { 44 | lhs = lhs / rhs 45 | } 46 | 47 | func min(_ a: (Float, Float, Float), _ b: (Float, Float, Float)) -> (Float, Float, Float) { 48 | return (min(a.0, b.0), min(a.1, b.1), min(a.2, b.2)) 49 | } 50 | 51 | func max(_ a: (Float, Float, Float), _ b: (Float, Float, Float)) -> (Float, Float, Float) { 52 | return (max(a.0, b.0), max(a.1, b.1), max(a.2, b.2)) 53 | } 54 | -------------------------------------------------------------------------------- /Swift/BlurHashTest/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Website/src/styles/base.scss: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | @include fluid-type(font-size, 20rem, 100rem, 0.8rem, 1rem); 4 | } 5 | 6 | body { 7 | width: 100%; 8 | min-height: 100vh; 9 | background: #fff; 10 | min-width: 320px; 11 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 12 | 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 13 | -webkit-tap-highlight-color: transparent; 14 | } 15 | 16 | strong, 17 | label, 18 | input, 19 | textarea, 20 | input, 21 | button { 22 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 23 | 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 24 | } 25 | ::-webkit-input-placeholder, 26 | ::-moz-placeholder, 27 | :-ms-input-placeholder, 28 | input:-moz-placeholder { 29 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 30 | 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 31 | color: $text-grey-light; 32 | } 33 | 34 | body, 35 | h1, 36 | h2, 37 | h3, 38 | h4, 39 | h5, 40 | h6, 41 | p, 42 | a, 43 | li, 44 | strong, 45 | label, 46 | input, 47 | textarea { 48 | color: $dark-grey; 49 | line-height: 1.4; 50 | text-rendering: optimizeLegibility; 51 | } 52 | 53 | h1 { 54 | font-size: 2rem; 55 | font-weight: 700; 56 | } 57 | h2 { 58 | font-size: 1.8rem; 59 | font-weight: 700; 60 | } 61 | h3 { 62 | font-size: 1.6rem; 63 | font-weight: 700; 64 | } 65 | h4 { 66 | font-size: 1.4rem; 67 | font-weight: 700; 68 | } 69 | h5 { 70 | font-size: 1.2rem; 71 | font-weight: 700; 72 | } 73 | h6 { 74 | font-size: 1rem; 75 | font-weight: 700; 76 | } 77 | 78 | body > svg { 79 | display: none; 80 | } 81 | 82 | p, 83 | label, 84 | textarea, 85 | input, 86 | label, 87 | select, 88 | button, 89 | textarea { 90 | font-size: 1rem; 91 | } 92 | -------------------------------------------------------------------------------- /TypeScript/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blurhash test file 6 | 43 | 44 | 45 | 46 |

BlurHash demo

47 |
48 |
49 |

Encode

50 | 51 | 55 | 61 |
62 |
63 |

Blurhash

64 | 65 |
66 |
67 |

Decode

68 | 69 |
70 |
71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /Swift/BlurHashTest/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Swift/BlurHashKit/Generation.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension BlurHash { 4 | public init(horizontalGradientFrom leftColour: UIColor, to rightColour: UIColor) { 5 | let average = (leftColour.linear + rightColour.linear) / 2 6 | let difference = (leftColour.linear - rightColour.linear) / 2 7 | self.components = [[average, difference]] 8 | } 9 | 10 | public init(verticalGradientFrom leftColour: UIColor, to rightColour: UIColor) { 11 | let average = (leftColour.linear + rightColour.linear) / 2 12 | let difference = (leftColour.linear - rightColour.linear) / 2 13 | self.components = [[average], [difference]] 14 | } 15 | 16 | public init(blendingTopLeft topLeftColour: UIColor, topRight topRightColour: UIColor, bottomLeft bottomLeftColour: UIColor, bottomRight bottomRightColour: UIColor) { 17 | let average = (topLeftColour.linear + topRightColour.linear + bottomLeftColour.linear + bottomRightColour.linear) / 4 18 | let horizontalDifference = (topLeftColour.linear - topRightColour.linear + bottomLeftColour.linear - bottomRightColour.linear) / 4 19 | let verticalDifference = (topLeftColour.linear + topRightColour.linear - bottomLeftColour.linear - bottomRightColour.linear) / 4 20 | let diagonalDifference = (topLeftColour.linear - topRightColour.linear - bottomLeftColour.linear + bottomRightColour.linear) / 4 21 | 22 | self.components = [[average, horizontalDifference], [verticalDifference, diagonalDifference]] 23 | } 24 | } 25 | 26 | extension UIColor { 27 | var linear: (Float, Float, Float) { 28 | guard let c = cgColor.converted(to: CGColorSpace(name: CGColorSpace.sRGB)!, intent: .defaultIntent, options: nil)?.components else { return (0, 0, 0) } 29 | 30 | switch c.count { 31 | case 1, 2: return (sRGBToLinear(c[0]), sRGBToLinear(c[0]), sRGBToLinear(c[0])) 32 | case 3, 4: return (sRGBToLinear(c[0]), sRGBToLinear(c[1]), sRGBToLinear(c[2])) 33 | default: return (0, 0, 0) 34 | } 35 | } 36 | } 37 | 38 | func sRGBToLinear(_ value: CGFloat) -> Float { 39 | let v = Float(value) 40 | if v <= 0.04045 { return v / 12.92 } 41 | else { return pow((v + 0.055) / 1.055, 2.4) } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Swift/BlurHashKit/BlurHash.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct BlurHash { 4 | public let components: [[(Float, Float, Float)]] 5 | 6 | public var numberOfHorizontalComponents: Int { return components.first!.count } 7 | public var numberOfVerticalComponents: Int { return components.count } 8 | 9 | public func punch(_ factor: Float) -> BlurHash { 10 | return BlurHash(components: components.enumerated().map { (y, xComponents) in 11 | return xComponents.enumerated().map { (x, component) in 12 | if x == 0 && y == 0 { 13 | return component 14 | } else { 15 | return (component.0 * factor, component.1 * factor, component.2 * factor) 16 | } 17 | } 18 | }) 19 | } 20 | } 21 | 22 | public func +(lhs: BlurHash, rhs: BlurHash) throws -> BlurHash { 23 | return BlurHash(components: paddedZip(lhs.components, rhs.components, [], []).map { 24 | paddedZip($0.0, $0.1, (0, 0, 0) as (Float, Float, Float), (0, 0, 0) as (Float, Float, Float)).map { ($0.0.0 + $0.1.0, $0.0.1 + $0.1.1, $0.0.2 + $0.1.2) } 25 | }) 26 | } 27 | 28 | public func -(lhs: BlurHash, rhs: BlurHash) throws -> BlurHash { 29 | return BlurHash(components: paddedZip(lhs.components, rhs.components, [], []).map { 30 | paddedZip($0.0, $0.1, (0, 0, 0) as (Float, Float, Float), (0, 0, 0) as (Float, Float, Float)).map { ($0.0.0 - $0.1.0, $0.0.1 - $0.1.1, $0.0.2 - $0.1.2) } 31 | }) 32 | } 33 | 34 | private func paddedZip(_ collection1: Collection1, _ collection2: Collection2, _ padding1: Collection1.Element, _ padding2: Collection2.Element) -> Zip2Sequence<[Collection1.Element], [Collection2.Element]> where Collection1: Collection, Collection2: Collection { 35 | if collection1.count < collection2.count { 36 | let padded = collection1 + Array(repeating: padding1, count: collection2.count - collection1.count) 37 | return zip(padded, Array(collection2)) 38 | } else if collection2.count < collection1.count { 39 | let padded = collection2 + Array(repeating: padding2, count: collection1.count - collection2.count) 40 | return zip(Array(collection1), padded) 41 | } else { 42 | return zip(Array(collection1), Array(collection2)) 43 | } 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Swift/Readme.md: -------------------------------------------------------------------------------- 1 | # BlurHash for iOS, in Swift 2 | 3 | ## Standalone decoder and encoder 4 | 5 | [BlurHashDecode.swift](BlurHashDecode.swift) and [BlurHashEncode.swift](BlurHashEncode.swift) contain a decoder 6 | and encoder for BlurHash to and from `UIImage`. Both files are completeiy standalone, and can simply be copied into your 7 | project directly. 8 | 9 | ### Decoding 10 | 11 | [BlurHashDecode.swift](BlurHashDecode.swift) implements the following extension on `UIImage`: 12 | 13 | public convenience init?(blurHash: String, size: CGSize, punch: Float = 1) 14 | 15 | This creates a UIImage containing the placeholder image decoded from the BlurHash string, or returns nil if decoding failed. 16 | The parameters are: 17 | 18 | * `blurHash` - A string containing the BlurHash. 19 | * `size` - The requested output size. You should keep this small, and let UIKit scale it up for you. 32 pixels wide is plenty. 20 | * `punch` - Adjusts the contrast of the output image. Tweak it if you want a different look for your placeholders. 21 | 22 | ### Encoding 23 | 24 | [BlurHashEncode.swift](BlurHashEncode.swift) implements the following extension on `UIImage`: 25 | 26 | public func blurHash(numberOfComponents components: (Int, Int)) -> String? 27 | 28 | This returns a string containing the BlurHash for the image, or nil if the image was in a weird format that is not supported. 29 | The parameters are: 30 | 31 | * `numberOfComponents` - a Tuple of integers specifying the number of components in the X and Y directions. Both must be 32 | between 1 and 9 inclusive, or the function will return nil. 3 to 5 is usually a good range. 33 | 34 | ## BlurHashKit 35 | 36 | This is a more advanced library, currently in development. It will let you do more advanced operations using BlurHashes, 37 | such testing whether various parts of an image are dark and light, or generating BlurHashes as gradients from corner colours. 38 | 39 | It is currently not documented or finalised, but feel free to look into the different files and what they implement, or look at 40 | how it is used by the test app. 41 | 42 | ## BlurHashTest.app 43 | 44 | This is a simple test app that shows how to use the various pieces of BlurHash functionality, and lets you play with the 45 | algorithm. 46 | -------------------------------------------------------------------------------- /Swift/BlurHashKit/FromUIImage.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public extension BlurHash { 4 | init?(image: UIImage, numberOfComponents components: (Int, Int)) { 5 | guard components.0 >= 1, components.0 <= 9, 6 | components.1 >= 1, components.1 <= 9 else { 7 | fatalError("Number of components bust be between 1 and 9 inclusive on each axis") 8 | } 9 | 10 | guard let cgImage = image.cgImage, 11 | let dataProvider = cgImage.dataProvider, 12 | let data = dataProvider.data, 13 | let pixels = CFDataGetBytePtr(data), 14 | cgImage.colorSpace?.numberOfComponents == 3, 15 | cgImage.bitsPerPixel == 24 || cgImage.bitsPerPixel == 32 else { 16 | assertionFailure("Invalid image format") 17 | return nil 18 | } 19 | 20 | let width = cgImage.width 21 | let height = cgImage.height 22 | let bytesPerRow = cgImage.bytesPerRow 23 | 24 | self.components = (0 ..< components.1).map { y in 25 | return (0 ..< components.0).map { x in 26 | let normalisation: Float = (x == 0 && y == 0) ? 1 : 2 27 | return BlurHash.multiplyBasisFunction(pixels: pixels, width: width, height: height, bytesPerRow: bytesPerRow, bytesPerPixel: cgImage.bitsPerPixel / 8, pixelOffset: 0) { 28 | normalisation * cos(Float.pi * Float(x) * $0 / Float(width)) * cos(Float.pi * Float(y) * $1 / Float(height)) 29 | } 30 | } 31 | } 32 | } 33 | 34 | static private func multiplyBasisFunction(pixels: UnsafePointer, width: Int, height: Int, bytesPerRow: Int, bytesPerPixel: Int, pixelOffset: Int, basisFunction: (Float, Float) -> Float) -> (Float, Float, Float) { 35 | var r: Float = 0 36 | var g: Float = 0 37 | var b: Float = 0 38 | 39 | let buffer = UnsafeBufferPointer(start: pixels, count: height * bytesPerRow) 40 | 41 | for x in 0 ..< width { 42 | for y in 0 ..< height { 43 | let basis = basisFunction(Float(x), Float(y)) 44 | r += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 0 + y * bytesPerRow]) 45 | g += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 1 + y * bytesPerRow]) 46 | b += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 2 + y * bytesPerRow]) 47 | } 48 | } 49 | 50 | let scale = 1 / Float(width * height) 51 | 52 | return (r * scale, g * scale, b * scale) 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Swift/BlurHashKit/FromString.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public extension BlurHash { 4 | init?(string: String) { 5 | guard string.count >= 6 else { return nil } 6 | 7 | let sizeFlag = String(string[0]).decode83() 8 | let numY = (sizeFlag / 9) + 1 9 | let numX = (sizeFlag % 9) + 1 10 | 11 | let quantisedMaximumValue = String(string[1]).decode83() 12 | let maximumValue = Float(quantisedMaximumValue + 1) / 166 13 | 14 | guard string.count == 4 + 2 * numX * numY else { return nil } 15 | 16 | self.components = (0 ..< numY).map { y in 17 | return (0 ..< numX).map { x in 18 | if x == 0 && y == 0 { 19 | let value = String(string[2 ..< 6]).decode83() 20 | return BlurHash.decodeDC(value) 21 | } else { 22 | let i = x + y * numX 23 | let value = String(string[4 + i * 2 ..< 4 + i * 2 + 2]).decode83() 24 | return BlurHash.decodeAC(value, maximumValue: maximumValue) 25 | } 26 | } 27 | } 28 | } 29 | 30 | private static func decodeDC(_ value: Int) -> (Float, Float, Float) { 31 | let intR = value >> 16 32 | let intG = (value >> 8) & 255 33 | let intB = value & 255 34 | return (sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB)) 35 | } 36 | 37 | private static func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) { 38 | let quantR = value / (19 * 19) 39 | let quantG = (value / 19) % 19 40 | let quantB = value % 19 41 | 42 | let rgb = ( 43 | signPow((Float(quantR) - 9) / 9, 2) * maximumValue, 44 | signPow((Float(quantG) - 9) / 9, 2) * maximumValue, 45 | signPow((Float(quantB) - 9) / 9, 2) * maximumValue 46 | ) 47 | 48 | return rgb 49 | } 50 | } 51 | 52 | private extension String { 53 | subscript (offset: Int) -> Character { 54 | return self[index(startIndex, offsetBy: offset)] 55 | } 56 | 57 | subscript (bounds: CountableClosedRange) -> Substring { 58 | let start = index(startIndex, offsetBy: bounds.lowerBound) 59 | let end = index(startIndex, offsetBy: bounds.upperBound) 60 | return self[start...end] 61 | } 62 | 63 | subscript (bounds: CountableRange) -> Substring { 64 | let start = index(startIndex, offsetBy: bounds.lowerBound) 65 | let end = index(startIndex, offsetBy: bounds.upperBound) 66 | return self[start.. { 40 | const imageData = ctx.getImageData( 41 | 0, 42 | 0, 43 | originalCanvas.width, 44 | originalCanvas.height 45 | ); 46 | const blurhash = encode( 47 | imageData.data, 48 | imageData.width, 49 | imageData.height, 50 | componentX, 51 | componentY 52 | ); 53 | blurhashElement.value = blurhash; 54 | render(); 55 | }, 0); 56 | }; 57 | img.src = URL.createObjectURL(fileInput.files[0]); 58 | } 59 | } 60 | 61 | blurhashElement.addEventListener("keyup", render); 62 | fileInput.addEventListener("change", doEncode); 63 | componentXElement.addEventListener("change", doEncode); 64 | componentYElement.addEventListener("change", doEncode); 65 | 66 | render(); 67 | -------------------------------------------------------------------------------- /Website/assets/svg/wolt-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Website/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 5 | 6 | // Is the current build a development build 7 | const env = process.env.NODE_ENV; 8 | 9 | const IS_DEV = !env ? true : env; 10 | const dirNode = 'node_modules'; 11 | const dirApp = path.join(__dirname, 'src'); 12 | const dirAssets = path.join(__dirname, 'assets'); 13 | 14 | /** 15 | * Webpack Configuration 16 | */ 17 | module.exports = { 18 | entry: { 19 | blurhash: path.join(dirApp, 'index'), 20 | }, 21 | resolve: { 22 | modules: [dirNode, dirApp, dirAssets], 23 | }, 24 | output: { 25 | filename: '[name].[hash].js', 26 | }, 27 | devtool: IS_DEV ? '#cheap-module-source-map' : '#source-map', 28 | plugins: [ 29 | new MiniCssExtractPlugin({ 30 | filename: '[name].[hash].css', 31 | }), 32 | new webpack.DefinePlugin({ 33 | IS_DEV: IS_DEV, 34 | }), 35 | new webpack.HotModuleReplacementPlugin(), 36 | new HtmlWebpackPlugin({ 37 | template: path.join(dirApp, 'index.ejs'), 38 | title: 'BlurHash', 39 | }), 40 | ], 41 | module: { 42 | rules: [ 43 | // BABEL 44 | { 45 | test: /\.js$/, 46 | loader: 'babel-loader', 47 | exclude: /(node_modules)/, 48 | options: { 49 | compact: true, 50 | }, 51 | }, 52 | 53 | // CSS / SASS 54 | { 55 | test: /\.scss/, 56 | use: [ 57 | IS_DEV && MiniCssExtractPlugin.loader, 58 | { 59 | loader: 'css-loader', 60 | options: { 61 | sourceMap: IS_DEV, 62 | }, 63 | }, 64 | { 65 | loader: 'sass-loader', 66 | options: { 67 | sourceMap: IS_DEV, 68 | includePaths: [dirAssets], 69 | }, 70 | }, 71 | ], 72 | }, 73 | // IMAGES 74 | { 75 | test: /\.(jpe?g|png|gif|svg)$/, 76 | loader: 'file-loader', 77 | options: { 78 | name: '[path][name].[ext]', 79 | }, 80 | }, 81 | 82 | // FONTS 83 | { 84 | test: /\.(eot|otf|woff2?|ttf)[\?]?.*$/, // eslint-disable-line 85 | use: 'file-loader?name=fonts/[name].[ext]', 86 | }, 87 | ], 88 | }, 89 | devServer: { 90 | host: '0.0.0.0', 91 | historyApiFallback: true, 92 | hot: true, 93 | }, 94 | }; 95 | -------------------------------------------------------------------------------- /Swift/BlurHashTest/SimpleViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class SimpleViewController: UIViewController { 4 | @IBOutlet weak var originalImageView: UIImageView? 5 | @IBOutlet weak var hashLabel: UILabel? 6 | @IBOutlet weak var blurImageView: UIImageView? 7 | @IBOutlet weak var xComponentsLabel: UILabel? 8 | @IBOutlet weak var yComponentsLabel: UILabel? 9 | 10 | let images: [UIImage] = [ 11 | UIImage(named: "pic2.png")!, 12 | UIImage(named: "pic1.png")!, 13 | UIImage(named: "pic3.png")!, 14 | UIImage(named: "pic6.png")!, 15 | ] 16 | 17 | var imageIndex: Int = 0 18 | var xComponents: Int = 4 19 | var yComponents: Int = 3 20 | var blurHash: String = "" 21 | var punch: Float = 1 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | updateEncode() 27 | updateDecode() 28 | } 29 | 30 | @IBAction func imageTapped() { 31 | imageIndex = (imageIndex + 1) % images.count 32 | updateEncode() 33 | updateDecode() 34 | } 35 | 36 | @IBAction func xPlusTapped() { 37 | if xComponents < 8 { 38 | xComponents += 1 39 | updateEncode() 40 | updateDecode() 41 | } 42 | } 43 | 44 | @IBAction func xMinusTapped() { 45 | if xComponents > 1 { 46 | xComponents -= 1 47 | updateEncode() 48 | updateDecode() 49 | } 50 | } 51 | 52 | @IBAction func yPlusTapped() { 53 | if yComponents < 8 { 54 | yComponents += 1 55 | updateEncode() 56 | updateDecode() 57 | } 58 | } 59 | 60 | @IBAction func yMinusTapped() { 61 | if yComponents > 1 { 62 | yComponents -= 1 63 | updateEncode() 64 | updateDecode() 65 | } 66 | } 67 | 68 | 69 | @IBAction func sliderChanged(slider: UISlider) { 70 | punch = slider.value 71 | updateDecode() 72 | } 73 | 74 | func updateEncode() { 75 | originalImageView?.image = images[imageIndex] 76 | blurHash = images[imageIndex].blurHash(numberOfComponents: (xComponents, yComponents))! 77 | hashLabel?.text = blurHash 78 | xComponentsLabel?.text = String(xComponents) 79 | yComponentsLabel?.text = String(yComponents) 80 | } 81 | 82 | func updateDecode() { 83 | let blurImage = UIImage(blurHash: blurHash, size: CGSize(width: 32, height: 32), punch: punch) 84 | 85 | blurImageView?.image = blurImage 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /TypeScript/README.md: -------------------------------------------------------------------------------- 1 | # blurhash 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/blurhash.svg?style=flat)](https://npmjs.org/package/blurhash) 4 | [![NPM Downloads](https://img.shields.io/npm/dm/blurhash.svg?style=flat)](https://npmjs.org/package/blurhash) 5 | 6 | > JavaScript encoder and decoder for the [Wolt BlurHash](https://github.com/woltapp/blurhash) algorithm 7 | 8 | ## Install 9 | 10 | ```sh 11 | npm install --save blurhash 12 | ``` 13 | 14 | See [react-blurhash](https://github.com/woltapp/react-blurhash) to use blurhash with React. 15 | 16 | ## API 17 | 18 | ### `decode(blurhash: string, width: number, height: number, punch?: number) => Uint8ClampedArray` 19 | 20 | > Decodes a blurhash string to pixels 21 | 22 | #### Example 23 | 24 | ```js 25 | import { decode } from "blurhash"; 26 | 27 | const pixels = decode("LEHV6nWB2yk8pyo0adR*.7kCMdnj", 32, 32); 28 | 29 | const canvas = document.createElement("canvas"); 30 | const ctx = canvas.getContext("2d"); 31 | const imageData = ctx.createImageData(width, height); 32 | imageData.data.set(pixels); 33 | ctx.putImageData(imageData, 0, 0); 34 | document.body.append(canvas); 35 | ``` 36 | 37 | ### `encode(pixels: Uint8ClampedArray, width: number, height: number, componentX: number, componentY: number) => string` 38 | 39 | > Encodes pixels to a blurhash string 40 | 41 | ```js 42 | import { encode } from "blurhash"; 43 | 44 | const loadImage = async src => 45 | new Promise((resolve, reject) => { 46 | const img = new Image(); 47 | img.onload = () => resolve(img); 48 | img.onerror = (...args) => reject(args); 49 | img.src = src; 50 | }); 51 | 52 | const getImageData = image => { 53 | const canvas = document.createElement("canvas"); 54 | canvas.width = image.width; 55 | canvas.height = image.height; 56 | const context = canvas.getContext("2d"); 57 | context.drawImage(image, 0, 0); 58 | return context.getImageData(0, 0, image.width, image.height); 59 | }; 60 | 61 | const encodeImageToBlurhash = async imageUrl => { 62 | const image = await loadImage(imageUrl); 63 | const imageData = getImageData(image); 64 | return encode(imageData.data, imageData.width, imageData.height, 4, 4); 65 | }; 66 | ``` 67 | 68 | ### `isBlurhashValid(blurhash: string) => { result: boolean; errorReason?: string }` 69 | 70 | ```js 71 | import { isBlurhashValid } from "blurhash"; 72 | 73 | const validRes = isBlurhashValid("LEHV6nWB2yk8pyo0adR*.7kCMdnj"); 74 | // { result: true } 75 | 76 | const invalidRes = isBlurhashValid("???"); 77 | // { result: false, errorReason: "The blurhash string must be at least 6 characters" } 78 | ``` 79 | -------------------------------------------------------------------------------- /Kotlin/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /Website/src/sections/hero.js: -------------------------------------------------------------------------------- 1 | import { imageHashes } from '../constants'; 2 | import decode from '../../../TypeScript/dist/decode'; 3 | import Velocity from 'velocity-animate'; 4 | import smoothscroll from 'smoothscroll-polyfill'; 5 | 6 | // kick off the polyfill! 7 | smoothscroll.polyfill(); 8 | 9 | function hero() { 10 | const images = document.getElementsByClassName('image-bg'); 11 | const imageContainer = document.getElementsByClassName('imagesContainer'); 12 | const content = document.getElementsByClassName('content'); 13 | 14 | function render(canvas, blurhash) { 15 | if (blurhash) { 16 | const pixels = decode(blurhash, 32, 32); 17 | if (pixels) { 18 | const ctx = canvas.getContext('2d'); 19 | 20 | const imageData = new ImageData(pixels, 32, 32); 21 | ctx.putImageData(imageData, 0, 0); 22 | } 23 | } 24 | } 25 | drawBlurHash(); 26 | function drawBlurHash() { 27 | if (!document.readyState === 'complete') { 28 | return; 29 | } 30 | init(); 31 | const canvases = document.getElementsByClassName('image-canvas-bg'); 32 | 33 | if (canvases && canvases.length) { 34 | for (let i = 0; i < canvases.length; i++) { 35 | render(canvases[i], imageHashes[i]); 36 | } 37 | } 38 | startAnimation(); 39 | } 40 | 41 | function init() { 42 | Velocity({ 43 | elements: imageContainer, 44 | properties: { translateY: '25%' }, 45 | options: { 46 | duration: 0, 47 | }, 48 | }); 49 | Velocity({ 50 | elements: content, 51 | properties: { opacity: 0 }, 52 | options: { 53 | duration: 0, 54 | }, 55 | }); 56 | } 57 | 58 | function startAnimation() { 59 | for (let i = 0; i < images.length; i++) { 60 | images[i].classList.add('animateImages'); 61 | } 62 | Velocity({ 63 | elements: imageContainer, 64 | properties: { translateY: '0%' }, 65 | options: { 66 | duration: 750, 67 | delay: 500, 68 | easing: 'easeInOutCubic', 69 | }, 70 | }); 71 | Velocity({ 72 | elements: content, 73 | properties: { opacity: 1 }, 74 | options: { 75 | duration: 250, 76 | delay: 1000, 77 | complete: startParallax, 78 | }, 79 | }); 80 | } 81 | 82 | const translateY = amount => `translate3d(0%, ${amount}px, 0)`; 83 | 84 | function startParallax() { 85 | window.addEventListener('scroll', scrollImages); 86 | } 87 | 88 | function scrollImages(e) { 89 | const transform = window.pageYOffset * 0.5; 90 | imageContainer[0].style.transform = translateY(transform); 91 | imageContainer[0].style.webkitTransform = translateY(transform); 92 | } 93 | 94 | document.getElementById("get-started").addEventListener('click', () => { 95 | document.querySelector(".why-blurhash").scrollIntoView({ behavior: 'smooth' }); 96 | }) 97 | } 98 | 99 | export default hero; 100 | -------------------------------------------------------------------------------- /Website/src/sections/demo.js: -------------------------------------------------------------------------------- 1 | import { encode, decode } from '../../../TypeScript/dist/'; 2 | 3 | const blurhashElement = document.getElementById('demo-blurhash'); 4 | const canvas = document.getElementById('demo-canvas'); 5 | const originalCanvas = document.getElementById('original-canvas'); 6 | const fileInput = document.getElementById('file-upload'); 7 | const componentXElement = document.getElementById('component-x'); 8 | const componentYElement = document.getElementById('component-y'); 9 | const predefined = document.querySelector('.predefined'); 10 | 11 | function render() { 12 | const blurhash = blurhashElement.textContent; 13 | if (blurhash) { 14 | const pixels = decode(blurhash, 32, 32); 15 | if (pixels) { 16 | blurhashElement.classList.remove('error'); 17 | const ctx = canvas.getContext('2d'); 18 | 19 | const imageData = new ImageData(pixels, 32, 32); 20 | ctx.putImageData(imageData, 0, 0); 21 | } else { 22 | blurhashElement.classList.add('error'); 23 | } 24 | } 25 | } 26 | 27 | function clamp(n) { 28 | return isNaN(n) ? 1 : Math.min(9, Math.max(1, n)); 29 | } 30 | 31 | function renderSelectedFile() { 32 | const file = fileInput.files[0]; 33 | if (file) { 34 | var img = new Image(); 35 | originalCanvas.classList.add('visible'); 36 | img.onload = function() { 37 | renderImage(img); 38 | }; 39 | img.src = URL.createObjectURL(fileInput.files[0]); 40 | } 41 | } 42 | 43 | function renderImage(img) { 44 | const ctx = originalCanvas.getContext('2d'); 45 | 46 | ctx.drawImage(img, 0, 0, originalCanvas.width, originalCanvas.height); 47 | URL.revokeObjectURL(img.src); 48 | 49 | setTimeout(renderBlurhash, 0); 50 | } 51 | 52 | function renderBlurhash() { 53 | const ctx = originalCanvas.getContext('2d'); 54 | const componentX = clamp(+componentXElement.value); 55 | const componentY = clamp(+componentYElement.value); 56 | 57 | const imageData = ctx.getImageData(0, 0, originalCanvas.width, originalCanvas.height); 58 | const blurhash = encode( 59 | imageData.data, 60 | imageData.width, 61 | imageData.height, 62 | componentX, 63 | componentY, 64 | ); 65 | blurhashElement.textContent = blurhash; 66 | render(); 67 | } 68 | 69 | function renderSelectedImage() { 70 | console.log('renderSelectedImage'); 71 | const firstPredefinedImage = document.querySelector('.predefined input:checked + img'); 72 | originalCanvas.classList.remove('visible'); 73 | fileInput.value = ''; 74 | renderImage(firstPredefinedImage); 75 | } 76 | 77 | blurhashElement.addEventListener('change', render); 78 | blurhashElement.addEventListener('keyup', render); 79 | fileInput.addEventListener('change', renderSelectedFile); 80 | componentXElement.addEventListener('keyup', renderBlurhash); 81 | componentYElement.addEventListener('keyup', renderBlurhash); 82 | predefined.addEventListener('change', renderSelectedImage); 83 | originalCanvas.addEventListener('click', renderSelectedImage); 84 | 85 | export default function() { 86 | renderSelectedImage(); 87 | } 88 | -------------------------------------------------------------------------------- /Swift/BlurHashKit/ToUIImage.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension BlurHash { 4 | public func cgImage(size: CGSize) -> CGImage? { 5 | let width = Int(size.width) 6 | let height = Int(size.height) 7 | let bytesPerRow = width * 3 8 | 9 | guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { return nil } 10 | CFDataSetLength(data, bytesPerRow * height) 11 | 12 | guard let pixels = CFDataGetMutableBytePtr(data) else { return nil } 13 | 14 | for y in 0 ..< height { 15 | for x in 0 ..< width { 16 | var c: (Float, Float, Float) = (0, 0, 0) 17 | 18 | for j in 0 ..< numberOfVerticalComponents { 19 | for i in 0 ..< numberOfHorizontalComponents { 20 | let basis = cos(Float.pi * Float(x) * Float(i) / Float(width)) * cos(Float.pi * Float(y) * Float(j) / Float(height)) 21 | let component = components[j][i] 22 | c += component * basis 23 | } 24 | } 25 | 26 | let intR = UInt8(linearTosRGB(c.0)) 27 | let intG = UInt8(linearTosRGB(c.1)) 28 | let intB = UInt8(linearTosRGB(c.2)) 29 | 30 | pixels[3 * x + 0 + y * bytesPerRow] = intR 31 | pixels[3 * x + 1 + y * bytesPerRow] = intG 32 | pixels[3 * x + 2 + y * bytesPerRow] = intB 33 | } 34 | } 35 | 36 | let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue) 37 | 38 | guard let provider = CGDataProvider(data: data) else { return nil } 39 | guard let cgImage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 24, bytesPerRow: bytesPerRow, 40 | space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) else { return nil } 41 | 42 | return cgImage 43 | } 44 | 45 | public func cgImage(numberOfPixels: Int = 1024, originalSize size: CGSize) -> CGImage? { 46 | let width: CGFloat 47 | let height: CGFloat 48 | if size.width > size.height { 49 | width = floor(sqrt(CGFloat(numberOfPixels) * size.width / size.height) + 0.5) 50 | height = floor(CGFloat(numberOfPixels) / width + 0.5) 51 | } else { 52 | height = floor(sqrt(CGFloat(numberOfPixels) * size.height / size.width) + 0.5) 53 | width = floor(CGFloat(numberOfPixels) / height + 0.5) 54 | } 55 | return cgImage(size: CGSize(width: width, height: height)) 56 | } 57 | 58 | public func image(size: CGSize) -> UIImage? { 59 | guard let cgImage = cgImage(size: size) else { return nil } 60 | return UIImage(cgImage: cgImage) 61 | } 62 | 63 | public func image(numberOfPixels: Int = 1024, originalSize size: CGSize) -> UIImage? { 64 | guard let cgImage = cgImage(numberOfPixels: numberOfPixels, originalSize: size) else { return nil } 65 | return UIImage(cgImage: cgImage) 66 | } 67 | } 68 | 69 | @objc extension UIImage { 70 | public convenience init?(blurHash string: String, size: CGSize, punch: Float = 1) { 71 | guard let blurHash = BlurHash(string: string), 72 | let cgImage = blurHash.punch(punch).cgImage(size: size) else { return nil } 73 | self.init(cgImage: cgImage) 74 | } 75 | 76 | public convenience init?(blurHash string: String, numberOfPixels: Int = 1024, originalSize size: CGSize, punch: Float = 1) { 77 | guard let blurHash = BlurHash(string: string), 78 | let cgImage = blurHash.punch(punch).cgImage(numberOfPixels: numberOfPixels, originalSize: size) else { return nil } 79 | self.init(cgImage: cgImage) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /CodeOfConduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at dag.agren@wolt.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /Algorithm.md: -------------------------------------------------------------------------------- 1 | # BlurHash Algorithm 2 | 3 | ## Summary 4 | 5 | BlurHash applies a simple [DCT transform](https://en.wikipedia.org/wiki/Discrete_cosine_transform) to the image data, 6 | keeping only the first few components, and then encodes these components using a base 83 encoding, with a JSON, 7 | HTML and shell-safe character set. The DC component, which represents the average colour of the image, is stored exactly 8 | as an sRGB value, for easy use without impleneting the full algorithm. The AC components are encoded lossily. 9 | 10 | ## Reference implementation 11 | 12 | [Simplified Swift decoder implemenation.](Swift/BlurHashDecode.swift) 13 | 14 | [Simplified Swift encoder implemenation.](Swift/BlurHashEncode.swift) 15 | 16 | ## Structure 17 | 18 | Here follows an example of a BlurHash string, with the different parts labelled: 19 | 20 | Example: LlMF%n00%#MwS|WCWEM{R*bbWBbH 21 | Legend: 12333344.................... 22 | 23 | 1. **Number of components, 1 digit.** 24 | 25 | For a BlurHash with `nx` components along the X axis and `ny` components along the Y axis, this is equal to `(nx - 1) + (ny - 1) * 9`. 26 | 27 | 2. **Maximum AC component value, 1 digit.** 28 | 29 | All AC components are scaled by this value. It represents a floating-point value of `(max + 1) / 166`. 30 | 31 | 3. **Average colour. 4 digits.** 32 | 33 | The average colour of the image in sRGB space, encoded as a 24-bit RGB value, with R in the most signficant position. This value can 34 | be used directly if you only want the average colour rather than the full DCT-encoded image. 35 | 36 | 4. **AC components, 2 digits each, `nx * ny - 1` components in total.** 37 | 38 | The AC components of the DCT transform, ordred by increasing X first, then Y. They are encoded as three values for `R`, `G` and `B`, 39 | each between 0 and 18. They are combined together as `R * 19^2 + G * 19 + B`, for a total range of 0 to 6859. 40 | 41 | Each value represents a floating-point value between -1 and 1. 0-8 represent negative values, 9 represents zero, and 10-18 42 | represent positive values. Positive values are encoded as `((X - 9) / 9) ^ 2`, while negative 43 | values are encoded as `-((9 - X) / 9 ) ^ 2`. `^` represents exponentiation. This value is then multiplied by the maximum AC 44 | component value, field 2 above. 45 | 46 | ## Base 83 47 | 48 | A custom base 83 encoding is used. Values are encoded individually, using 1 to 4 digits, and concatenated together. Multiple-digit 49 | values are encoded in big-endian order, with the most signficant digit first. 50 | 51 | The character used set is `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~`. 52 | 53 | ## Discrete Cosine Transform 54 | 55 | To decode a single pixel of output, you loop over the DCT components and calculate a weighted sum of cosine functions. In 56 | pseudocode, for a normalised pixel position `x`, `y`, with each coordinate ranging from 0 to 1, and components `Cij` , 57 | you calculate the following for each of R, G and B: 58 | 59 | foreach j in 0 ... ny - 1 60 | foreach i in 0 ... nx - 1 61 | value = value + Cij * cos(x * i * pi) * cos(y * j * pi) 62 | 63 | The `C00` component is the DC component, while the others are the AC components. The DC component must first be converted 64 | from sRGB to linear RGB space. AC components are already linear. 65 | 66 | Once the R, G and B values have been calculated, the must be converted from linear to your output colourspace, usually sRGB. 67 | -------------------------------------------------------------------------------- /TypeScript/src/decode.ts: -------------------------------------------------------------------------------- 1 | import { decode83 } from "./base83"; 2 | import { sRGBToLinear, signPow, linearTosRGB } from "./utils"; 3 | import { ValidationError } from "./error"; 4 | 5 | /** 6 | * Returns an error message if invalid or undefined if valid 7 | * @param blurhash 8 | */ 9 | const validateBlurhash = (blurhash: string) => { 10 | if (!blurhash || blurhash.length < 6) { 11 | throw new ValidationError( 12 | "The blurhash string must be at least 6 characters" 13 | ); 14 | } 15 | 16 | const sizeFlag = decode83(blurhash[0]); 17 | const numY = Math.floor(sizeFlag / 9) + 1; 18 | const numX = (sizeFlag % 9) + 1; 19 | 20 | if (blurhash.length !== 4 + 2 * numX * numY) { 21 | throw new ValidationError( 22 | `blurhash length mismatch: length is ${ 23 | blurhash.length 24 | } but it should be ${4 + 2 * numX * numY}` 25 | ); 26 | } 27 | }; 28 | 29 | export const isBlurhashValid = ( 30 | blurhash: string 31 | ): { result: boolean; errorReason?: string } => { 32 | try { 33 | validateBlurhash(blurhash); 34 | } catch (error) { 35 | return { result: false, errorReason: error.message }; 36 | } 37 | 38 | return { result: true }; 39 | }; 40 | 41 | const decodeDC = (value: number) => { 42 | const intR = value >> 16; 43 | const intG = (value >> 8) & 255; 44 | const intB = value & 255; 45 | return [sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB)]; 46 | }; 47 | 48 | const decodeAC = (value: number, maximumValue: number) => { 49 | const quantR = Math.floor(value / (19 * 19)); 50 | const quantG = Math.floor(value / 19) % 19; 51 | const quantB = value % 19; 52 | 53 | const rgb = [ 54 | signPow((quantR - 9) / 9, 2.0) * maximumValue, 55 | signPow((quantG - 9) / 9, 2.0) * maximumValue, 56 | signPow((quantB - 9) / 9, 2.0) * maximumValue 57 | ]; 58 | 59 | return rgb; 60 | }; 61 | 62 | const decode = ( 63 | blurhash: string, 64 | width: number, 65 | height: number, 66 | punch?: number 67 | ) => { 68 | validateBlurhash(blurhash); 69 | 70 | punch = punch | 1; 71 | 72 | const sizeFlag = decode83(blurhash[0]); 73 | const numY = Math.floor(sizeFlag / 9) + 1; 74 | const numX = (sizeFlag % 9) + 1; 75 | 76 | const quantisedMaximumValue = decode83(blurhash[1]); 77 | const maximumValue = (quantisedMaximumValue + 1) / 166; 78 | 79 | const colors = new Array(numX * numY); 80 | 81 | for (let i = 0; i < colors.length; i++) { 82 | if (i === 0) { 83 | const value = decode83(blurhash.substring(2, 6)); 84 | colors[i] = decodeDC(value); 85 | } else { 86 | const value = decode83(blurhash.substring(4 + i * 2, 6 + i * 2)); 87 | colors[i] = decodeAC(value, maximumValue * punch); 88 | } 89 | } 90 | 91 | const bytesPerRow = width * 4; 92 | const pixels = new Uint8ClampedArray(bytesPerRow * height); 93 | 94 | for (let y = 0; y < height; y++) { 95 | for (let x = 0; x < width; x++) { 96 | let r = 0; 97 | let g = 0; 98 | let b = 0; 99 | 100 | for (let j = 0; j < numY; j++) { 101 | for (let i = 0; i < numX; i++) { 102 | const basis = 103 | Math.cos((Math.PI * x * i) / width) * 104 | Math.cos((Math.PI * y * j) / height); 105 | let color = colors[i + j * numX]; 106 | r += color[0] * basis; 107 | g += color[1] * basis; 108 | b += color[2] * basis; 109 | } 110 | } 111 | 112 | let intR = linearTosRGB(r); 113 | let intG = linearTosRGB(g); 114 | let intB = linearTosRGB(b); 115 | 116 | pixels[4 * x + 0 + y * bytesPerRow] = intR; 117 | pixels[4 * x + 1 + y * bytesPerRow] = intG; 118 | pixels[4 * x + 2 + y * bytesPerRow] = intB; 119 | pixels[4 * x + 3 + y * bytesPerRow] = 255; // alpha 120 | } 121 | } 122 | return pixels; 123 | }; 124 | 125 | export default decode; 126 | -------------------------------------------------------------------------------- /TypeScript/src/encode.ts: -------------------------------------------------------------------------------- 1 | import { encode83 } from "./base83"; 2 | import { sRGBToLinear, signPow, linearTosRGB } from "./utils"; 3 | import { ValidationError } from "./error"; 4 | 5 | type NumberTriplet = [number, number, number]; 6 | 7 | const bytesPerPixel = 4; 8 | 9 | const multiplyBasisFunction = ( 10 | pixels: Uint8ClampedArray, 11 | width: number, 12 | height: number, 13 | basisFunction: (i: number, j: number) => number 14 | ): NumberTriplet => { 15 | let r = 0; 16 | let g = 0; 17 | let b = 0; 18 | const bytesPerRow = width * bytesPerPixel; 19 | 20 | for (let x = 0; x < width; x++) { 21 | for (let y = 0; y < height; y++) { 22 | const basis = basisFunction(x, y); 23 | r += 24 | basis * sRGBToLinear(pixels[bytesPerPixel * x + 0 + y * bytesPerRow]); 25 | g += 26 | basis * sRGBToLinear(pixels[bytesPerPixel * x + 1 + y * bytesPerRow]); 27 | b += 28 | basis * sRGBToLinear(pixels[bytesPerPixel * x + 2 + y * bytesPerRow]); 29 | } 30 | } 31 | 32 | let scale = 1 / (width * height); 33 | 34 | return [r * scale, g * scale, b * scale]; 35 | }; 36 | 37 | const encodeDC = (value: NumberTriplet): number => { 38 | const roundedR = linearTosRGB(value[0]); 39 | const roundedG = linearTosRGB(value[1]); 40 | const roundedB = linearTosRGB(value[2]); 41 | return (roundedR << 16) + (roundedG << 8) + roundedB; 42 | }; 43 | 44 | const encodeAC = (value: NumberTriplet, maximumValue: number): number => { 45 | let quantR = Math.floor( 46 | Math.max( 47 | 0, 48 | Math.min(18, Math.floor(signPow(value[0] / maximumValue, 0.5) * 9 + 9.5)) 49 | ) 50 | ); 51 | let quantG = Math.floor( 52 | Math.max( 53 | 0, 54 | Math.min(18, Math.floor(signPow(value[1] / maximumValue, 0.5) * 9 + 9.5)) 55 | ) 56 | ); 57 | let quantB = Math.floor( 58 | Math.max( 59 | 0, 60 | Math.min(18, Math.floor(signPow(value[2] / maximumValue, 0.5) * 9 + 9.5)) 61 | ) 62 | ); 63 | 64 | return quantR * 19 * 19 + quantG * 19 + quantB; 65 | }; 66 | 67 | const encode = ( 68 | pixels: Uint8ClampedArray, 69 | width: number, 70 | height: number, 71 | componentX: number, 72 | componentY: number 73 | ): string => { 74 | if (componentX < 1 || componentX > 9 || componentY < 1 || componentY > 9) { 75 | throw new ValidationError("BlurHash must have between 1 and 9 components"); 76 | } 77 | if (width * height * 4 !== pixels.length) { 78 | throw new ValidationError("Width and height must match the pixels array"); 79 | } 80 | 81 | let factors: Array<[number, number, number]> = []; 82 | for (let y = 0; y < componentY; y++) { 83 | for (let x = 0; x < componentX; x++) { 84 | const normalisation = x == 0 && y == 0 ? 1 : 2; 85 | const factor = multiplyBasisFunction( 86 | pixels, 87 | width, 88 | height, 89 | (i: number, j: number) => 90 | normalisation * 91 | Math.cos((Math.PI * x * i) / width) * 92 | Math.cos((Math.PI * y * j) / height) 93 | ); 94 | factors.push(factor); 95 | } 96 | } 97 | 98 | const dc = factors[0]; 99 | const ac = factors.slice(1); 100 | 101 | let hash = ""; 102 | 103 | let sizeFlag = componentX - 1 + (componentY - 1) * 9; 104 | hash += encode83(sizeFlag, 1); 105 | 106 | let maximumValue: number; 107 | if (ac.length > 0) { 108 | let actualMaximumValue = Math.max(...ac.map(val => Math.max(...val))); 109 | let quantisedMaximumValue = Math.floor( 110 | Math.max(0, Math.min(82, Math.floor(actualMaximumValue * 166 - 0.5))) 111 | ); 112 | maximumValue = (quantisedMaximumValue + 1) / 166; 113 | hash += encode83(quantisedMaximumValue, 1); 114 | } else { 115 | maximumValue = 1; 116 | hash += encode83(0, 1); 117 | } 118 | 119 | hash += encode83(encodeDC(dc), 4); 120 | 121 | ac.forEach(factor => { 122 | hash += encode83(encodeAC(factor, maximumValue), 2); 123 | }); 124 | 125 | return hash; 126 | }; 127 | 128 | export default encode; 129 | -------------------------------------------------------------------------------- /Swift/BlurHashKit/ColourProbes.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension BlurHash { 4 | public func linearRgb(atX: Float) -> (Float, Float, Float) { 5 | return components[0].enumerated().reduce((0, 0, 0)) { (sum, xEnumerated) in 6 | let (x, component) = xEnumerated 7 | return sum + component * cos(Float.pi * Float(x) * atX) 8 | } 9 | } 10 | 11 | public func linearRgb(atY: Float) -> (Float, Float, Float) { 12 | return components.enumerated().reduce((0, 0, 0)) { (sum, yEnumerated) in 13 | let (y, xComponents) = yEnumerated 14 | return sum + xComponents[0] * cos(Float.pi * Float(y) * atY) 15 | } 16 | } 17 | 18 | public func linearRgb(at position: (Float, Float)) -> (Float, Float, Float) { 19 | return components.enumerated().reduce((0, 0, 0)) { (sum, yEnumerated) in 20 | let (y, xComponents) = yEnumerated 21 | return xComponents.enumerated().reduce(sum) { (sum, xEnumerated) in 22 | let (x, component) = xEnumerated 23 | return sum + component * cos(Float.pi * Float(x) * position.0) * cos(Float.pi * Float(y) * position.1) 24 | } 25 | } 26 | } 27 | 28 | public func linearRgb(from upperLeft: (Float, Float), to lowerRight: (Float, Float)) -> (Float, Float, Float) { 29 | return components.enumerated().reduce((0, 0, 0)) { (sum, yEnumerated) in 30 | let (y, xComponents) = yEnumerated 31 | return xComponents.enumerated().reduce(sum) { (sum, xEnumerated) in 32 | let (x, component) = xEnumerated 33 | let horizontalAverage: Float = x == 0 ? 1 : (sin(Float.pi * Float(x) * lowerRight.0) - sin(Float.pi * Float(x) * upperLeft.0)) / (Float(x) * Float.pi * (lowerRight.0 - upperLeft.0)) 34 | let veritcalAverage: Float = y == 0 ? 1 : (sin(Float.pi * Float(y) * lowerRight.1) - sin(Float.pi * Float(y) * upperLeft.1)) / (Float(y) * Float.pi * (lowerRight.1 - upperLeft.1)) 35 | return sum + component * horizontalAverage * veritcalAverage 36 | } 37 | } 38 | } 39 | 40 | public func linearRgb(at upperLeft: (Float, Float), size: (Float, Float)) -> (Float, Float, Float) { 41 | return linearRgb(from: upperLeft, to: (upperLeft.0 + size.0, upperLeft.1 + size.1)) 42 | } 43 | 44 | public var averageLinearRgb: (Float, Float, Float) { 45 | return components[0][0] 46 | } 47 | 48 | public var leftEdgeLinearRgb: (Float, Float, Float) { return linearRgb(atX: 0) } 49 | public var rightEdgeLinearRgb: (Float, Float, Float) { return linearRgb(atX: 1) } 50 | public var topEdgeLinearRgb: (Float, Float, Float) { return linearRgb(atY: 0) } 51 | public var bottomEdgeLinearRgb: (Float, Float, Float) { return linearRgb(atY: 1) } 52 | public var topLeftCornerLinearRgb: (Float, Float, Float) { return linearRgb(at: (0, 0)) } 53 | public var topRightCornerLinearRgb: (Float, Float, Float) { return linearRgb(at: (1, 0)) } 54 | public var bottomLeftCornerLinearRgb: (Float, Float, Float) { return linearRgb(at: (0, 1)) } 55 | public var bottomRightCornerLinearRgb: (Float, Float, Float) { return linearRgb(at: (1, 1)) } 56 | } 57 | 58 | extension BlurHash { 59 | public func isDark(linearRgb rgb: (Float, Float, Float)) -> Bool { 60 | return rgb.0 * 0.299 + rgb.1 * 0.587 + rgb.2 * 0.114 < 0.5 61 | } 62 | 63 | public var isDark: Bool { return isDark(linearRgb: averageLinearRgb) } 64 | 65 | public func isDark(atX x: Float) -> Bool { return isDark(linearRgb: linearRgb(atX: x)) } 66 | public func isDark(atY y: Float) -> Bool { return isDark(linearRgb: linearRgb(atY: y)) } 67 | public func isDark(at position: (Float, Float)) -> Bool { return isDark(linearRgb: linearRgb(at: position)) } 68 | public func isDark(from upperLeft: (Float, Float), to lowerRight: (Float, Float)) -> Bool { return isDark(linearRgb: linearRgb(from: upperLeft, to: lowerRight)) } 69 | public func isDark(at upperLeft: (Float, Float), size: (Float, Float)) -> Bool { return isDark(linearRgb: linearRgb(at: upperLeft, size: size)) } 70 | 71 | public var isLeftEdgeDark: Bool { return isDark(atX: 0) } 72 | public var isRightEdgeDark: Bool { return isDark(atX: 1) } 73 | public var isTopEdgeDark: Bool { return isDark(atY: 0) } 74 | public var isBottomEdgeDark: Bool { return isDark(atY: 1) } 75 | public var isTopLeftCornerDark: Bool { return isDark(at: (0, 0)) } 76 | public var isTopRightCornerDark: Bool { return isDark(at: (1, 0)) } 77 | public var isBottomLeftCornerDark: Bool { return isDark(at: (0, 1)) } 78 | public var isBottomRightCornerDark: Bool { return isDark(at: (1, 1)) } 79 | } 80 | -------------------------------------------------------------------------------- /Swift/BlurHashTest/AdvancedViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import BlurHashKit 3 | 4 | class AdvancedViewController: UIViewController { 5 | @IBOutlet weak var originalImageView: UIImageView? 6 | @IBOutlet weak var uncompressedBlurImageView: UIImageView? 7 | @IBOutlet weak var hashLabel: UILabel? 8 | @IBOutlet weak var compressedBlurImageView: UIImageView? 9 | 10 | @IBOutlet weak var darknessBlurImageView: UIImageView? 11 | @IBOutlet weak var topLeftCornerLabel: UILabel? 12 | @IBOutlet weak var topEdgeLabel: UILabel? 13 | @IBOutlet weak var topRightCornerLabel: UILabel? 14 | @IBOutlet weak var leftEdgeLabel: UILabel? 15 | @IBOutlet weak var centreLabel: UILabel? 16 | @IBOutlet weak var rightEdgeLabel: UILabel? 17 | @IBOutlet weak var bottomLeftCornerLabel: UILabel? 18 | @IBOutlet weak var bottomEdgeLabel: UILabel? 19 | @IBOutlet weak var bottomRightCornerLabel: UILabel? 20 | 21 | @IBOutlet weak var xComponentsLabel: UILabel? 22 | @IBOutlet weak var yComponentsLabel: UILabel? 23 | 24 | let images: [UIImage] = [ 25 | UIImage(named: "pic2.png")!, 26 | UIImage(named: "pic1.png")!, 27 | UIImage(named: "pic3.png")!, 28 | UIImage(named: "pic6.png")!, 29 | UIImage(named: "pic4.png")!, 30 | UIImage(named: "pic5.png")!, 31 | ] 32 | 33 | var imageIndex: Int = 0 34 | var xComponents: Int = 4 35 | var yComponents: Int = 3 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | update() 41 | } 42 | 43 | @IBAction func imageTapped() { 44 | imageIndex = (imageIndex + 1) % images.count 45 | update() 46 | } 47 | 48 | @IBAction func xPlusTapped() { 49 | if xComponents < 9 { 50 | xComponents += 1 51 | update() 52 | } 53 | } 54 | 55 | @IBAction func xMinusTapped() { 56 | if xComponents > 1 { 57 | xComponents -= 1 58 | update() 59 | } 60 | } 61 | 62 | @IBAction func yPlusTapped() { 63 | if yComponents < 9 { 64 | yComponents += 1 65 | update() 66 | } 67 | } 68 | 69 | @IBAction func yMinusTapped() { 70 | if yComponents > 1 { 71 | yComponents -= 1 72 | update() 73 | } 74 | } 75 | 76 | 77 | func update() { 78 | let image = images[imageIndex] 79 | 80 | originalImageView?.image = image 81 | 82 | let blurHash = BlurHash(image: images[imageIndex], numberOfComponents: (xComponents, yComponents))! 83 | uncompressedBlurImageView?.image = blurHash.image(numberOfPixels: 1024, originalSize: image.size) 84 | 85 | hashLabel?.text = blurHash.string 86 | let decodedBlurHash = BlurHash(string: blurHash.string)! 87 | 88 | compressedBlurImageView?.image = decodedBlurHash.image(numberOfPixels: 1024, originalSize: image.size) 89 | darknessBlurImageView?.image = decodedBlurHash.image(numberOfPixels: 1024, originalSize: image.size) 90 | 91 | setDarkness(label: topLeftCornerLabel, isDark: decodedBlurHash.isTopLeftCornerDark, light: "Ⓛ", dark: "Ⓓ") 92 | setDarkness(label: topEdgeLabel, isDark: decodedBlurHash.isTopEdgeDark, light: "------Light------", dark: "------Dark------") 93 | setDarkness(label: topRightCornerLabel, isDark: decodedBlurHash.isTopRightCornerDark, light: "Ⓛ", dark: "Ⓓ") 94 | setDarkness(label: leftEdgeLabel, isDark: decodedBlurHash.isLeftEdgeDark, light: "|\n|\nLight\n|\n|", dark: "|\n|\nDark\n|\n|") 95 | setDarkness(label: centreLabel, isDark: decodedBlurHash.isDark, light: "Light", dark: "Dark") 96 | setDarkness(label: rightEdgeLabel, isDark: decodedBlurHash.isRightEdgeDark, light: "|\n|\nLight\n|\n|", dark: "|\n|\nDark\n|\n|") 97 | setDarkness(label: bottomLeftCornerLabel, isDark: decodedBlurHash.isBottomLeftCornerDark, light: "Ⓛ", dark: "Ⓓ") 98 | setDarkness(label: bottomEdgeLabel, isDark: decodedBlurHash.isBottomEdgeDark, light: "------Light------", dark: "------Dark------") 99 | setDarkness(label: bottomRightCornerLabel, isDark: decodedBlurHash.isBottomRightCornerDark, light: "Ⓛ", dark: "Ⓓ") 100 | 101 | xComponentsLabel?.text = String(xComponents) 102 | yComponentsLabel?.text = String(yComponents) 103 | 104 | } 105 | 106 | func setDarkness(label: UILabel?, isDark: Bool, light: String, dark: String) { 107 | if isDark { 108 | label?.textColor = .white 109 | label?.text = dark 110 | } else { 111 | label?.textColor = .black 112 | label?.text = light 113 | } 114 | } 115 | 116 | } 117 | 118 | -------------------------------------------------------------------------------- /Kotlin/lib/src/main/java/com/wolt/blurhashkt/BlurHashDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.wolt.blurhashkt 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.Color 5 | import kotlin.math.PI 6 | import kotlin.math.cos 7 | import kotlin.math.pow 8 | import kotlin.math.withSign 9 | 10 | object BlurHashDecoder { 11 | 12 | fun decode(blurHash: String?, width: Int, height: Int, punch: Float = 1f): Bitmap? { 13 | if (blurHash == null || blurHash.length < 6) { 14 | return null 15 | } 16 | val numCompEnc = decode83(blurHash, 0, 1) 17 | val numCompX = (numCompEnc % 9) + 1 18 | val numCompY = (numCompEnc / 9) + 1 19 | if (blurHash.length != 4 + 2 * numCompX * numCompY) { 20 | return null 21 | } 22 | val maxAcEnc = decode83(blurHash, 1, 2) 23 | val maxAc = (maxAcEnc + 1) / 166f 24 | val colors = Array(numCompX * numCompY) { i -> 25 | if (i == 0) { 26 | val colorEnc = decode83(blurHash, 2, 6) 27 | decodeDc(colorEnc) 28 | } else { 29 | val from = 4 + i * 2 30 | val colorEnc = decode83(blurHash, from, from + 2) 31 | decodeAc(colorEnc, maxAc * punch) 32 | } 33 | } 34 | return composeBitmap(width, height, numCompX, numCompY, colors) 35 | } 36 | 37 | private fun decode83(str: String, from: Int = 0, to: Int = str.length): Int { 38 | var result = 0 39 | for (i in from until to) { 40 | val index = charMap[str[i]] ?: -1 41 | if (index != -1) { 42 | result = result * 83 + index 43 | } 44 | } 45 | return result 46 | } 47 | 48 | private fun decodeDc(colorEnc: Int): FloatArray { 49 | val r = colorEnc shr 16 50 | val g = (colorEnc shr 8) and 255 51 | val b = colorEnc and 255 52 | return floatArrayOf(srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)) 53 | } 54 | 55 | private fun srgbToLinear(colorEnc: Int): Float { 56 | val v = colorEnc / 255f 57 | return if (v <= 0.04045f) { 58 | (v / 12.92f) 59 | } else { 60 | ((v + 0.055f) / 1.055f).pow(2.4f) 61 | } 62 | } 63 | 64 | private fun decodeAc(value: Int, maxAc: Float): FloatArray { 65 | val r = value / (19 * 19) 66 | val g = (value / 19) % 19 67 | val b = value % 19 68 | return floatArrayOf( 69 | signedPow2((r - 9) / 9.0f) * maxAc, 70 | signedPow2((g - 9) / 9.0f) * maxAc, 71 | signedPow2((b - 9) / 9.0f) * maxAc 72 | ) 73 | } 74 | 75 | private fun signedPow2(value: Float) = value.pow(2f).withSign(value) 76 | 77 | private fun composeBitmap( 78 | width: Int, height: Int, 79 | numCompX: Int, numCompY: Int, 80 | colors: Array 81 | ): Bitmap { 82 | val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) 83 | for (y in 0 until height) { 84 | for (x in 0 until width) { 85 | var r = 0f 86 | var g = 0f 87 | var b = 0f 88 | for (j in 0 until numCompY) { 89 | for (i in 0 until numCompX) { 90 | val basis = (cos(PI * x * i / width) * cos(PI * y * j / height)).toFloat() 91 | val color = colors[j * numCompX + i] 92 | r += color[0] * basis 93 | g += color[1] * basis 94 | b += color[2] * basis 95 | } 96 | } 97 | bitmap.setPixel(x, y, Color.rgb(linearToSrgb(r), linearToSrgb(g), linearToSrgb(b))) 98 | } 99 | } 100 | return bitmap 101 | } 102 | 103 | private fun linearToSrgb(value: Float): Int { 104 | val v = value.coerceIn(0f, 1f) 105 | return if (v <= 0.0031308f) { 106 | (v * 12.92f * 255f + 0.5f).toInt() 107 | } else { 108 | ((1.055f * v.pow(1 / 2.4f) - 0.055f) * 255 + 0.5f).toInt() 109 | } 110 | } 111 | 112 | private val charMap = listOf( 113 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 114 | 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 115 | 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 116 | 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '#', '$', '%', '*', '+', ',', 117 | '-', '.', ':', ';', '=', '?', '@', '[', ']', '^', '_', '{', '|', '}', '~' 118 | ) 119 | .mapIndexed { i, c -> c to i } 120 | .toMap() 121 | 122 | } 123 | -------------------------------------------------------------------------------- /C/encode.c: -------------------------------------------------------------------------------- 1 | #include "encode.h" 2 | 3 | #include 4 | #include 5 | 6 | #ifndef M_PI 7 | #define M_PI 3.14159265358979323846 8 | #endif 9 | 10 | static float *multiplyBasisFunction(int xComponent, int yComponent, int width, int height, uint8_t *rgb, size_t bytesPerRow); 11 | static char *encode_int(int value, int length, char *destination); 12 | 13 | static int linearTosRGB(float value); 14 | static float sRGBToLinear(int value); 15 | static int encodeDC(float r, float g, float b); 16 | static int encodeAC(float r, float g, float b, float maximumValue); 17 | static float signPow(float value, float exp); 18 | 19 | const char *blurHashForPixels(int xComponents, int yComponents, int width, int height, uint8_t *rgb, size_t bytesPerRow) { 20 | static char buffer[2 + 4 + (9 * 9 - 1) * 2 + 1]; 21 | 22 | if(xComponents < 1 || xComponents > 9) return NULL; 23 | if(yComponents < 1 || yComponents > 9) return NULL; 24 | 25 | float factors[yComponents][xComponents][3]; 26 | memset(factors, 0, sizeof(factors)); 27 | 28 | for(int y = 0; y < yComponents; y++) { 29 | for(int x = 0; x < xComponents; x++) { 30 | float *factor = multiplyBasisFunction(x, y, width, height, rgb, bytesPerRow); 31 | factors[y][x][0] = factor[0]; 32 | factors[y][x][1] = factor[1]; 33 | factors[y][x][2] = factor[2]; 34 | } 35 | } 36 | 37 | float *dc = factors[0][0]; 38 | float *ac = dc + 3; 39 | int acCount = xComponents * yComponents - 1; 40 | char *ptr = buffer; 41 | 42 | int sizeFlag = (xComponents - 1) + (yComponents - 1) * 9; 43 | ptr = encode_int(sizeFlag, 1, ptr); 44 | 45 | float maximumValue; 46 | if(acCount > 0) { 47 | float actualMaximumValue = 0; 48 | for(int i = 0; i < acCount * 3; i++) { 49 | actualMaximumValue = fmaxf(fabsf(ac[i]), actualMaximumValue); 50 | } 51 | 52 | int quantisedMaximumValue = fmaxf(0, fminf(82, floorf(actualMaximumValue * 166 - 0.5))); 53 | maximumValue = ((float)quantisedMaximumValue + 1) / 166; 54 | ptr = encode_int(quantisedMaximumValue, 1, ptr); 55 | } else { 56 | maximumValue = 1; 57 | ptr = encode_int(0, 1, ptr); 58 | } 59 | 60 | ptr = encode_int(encodeDC(dc[0], dc[1], dc[2]), 4, ptr); 61 | 62 | for(int i = 0; i < acCount; i++) { 63 | ptr = encode_int(encodeAC(ac[i * 3 + 0], ac[i * 3 + 1], ac[i * 3 + 2], maximumValue), 2, ptr); 64 | } 65 | 66 | *ptr = 0; 67 | 68 | return buffer; 69 | } 70 | 71 | static float *multiplyBasisFunction(int xComponent, int yComponent, int width, int height, uint8_t *rgb, size_t bytesPerRow) { 72 | float r = 0, g = 0, b = 0; 73 | float normalisation = (xComponent == 0 && yComponent == 0) ? 1 : 2; 74 | 75 | for(int y = 0; y < height; y++) { 76 | for(int x = 0; x < width; x++) { 77 | float basis = cosf(M_PI * xComponent * x / width) * cosf(M_PI * yComponent * y / height); 78 | r += basis * sRGBToLinear(rgb[3 * x + 0 + y * bytesPerRow]); 79 | g += basis * sRGBToLinear(rgb[3 * x + 1 + y * bytesPerRow]); 80 | b += basis * sRGBToLinear(rgb[3 * x + 2 + y * bytesPerRow]); 81 | } 82 | } 83 | 84 | float scale = normalisation / (width * height); 85 | 86 | static float result[3]; 87 | result[0] = r * scale; 88 | result[1] = g * scale; 89 | result[2] = b * scale; 90 | 91 | return result; 92 | } 93 | 94 | static int linearTosRGB(float value) { 95 | float v = fmaxf(0, fminf(1, value)); 96 | if(v <= 0.0031308) return v * 12.92 * 255 + 0.5; 97 | else return (1.055 * powf(v, 1 / 2.4) - 0.055) * 255 + 0.5; 98 | } 99 | 100 | static float sRGBToLinear(int value) { 101 | float v = (float)value / 255; 102 | if(v <= 0.04045) return v / 12.92; 103 | else return powf((v + 0.055) / 1.055, 2.4); 104 | } 105 | 106 | static int encodeDC(float r, float g, float b) { 107 | int roundedR = linearTosRGB(r); 108 | int roundedG = linearTosRGB(g); 109 | int roundedB = linearTosRGB(b); 110 | return (roundedR << 16) + (roundedG << 8) + roundedB; 111 | } 112 | 113 | static int encodeAC(float r, float g, float b, float maximumValue) { 114 | int quantR = fmaxf(0, fminf(18, floorf(signPow(r / maximumValue, 0.5) * 9 + 9.5))); 115 | int quantG = fmaxf(0, fminf(18, floorf(signPow(g / maximumValue, 0.5) * 9 + 9.5))); 116 | int quantB = fmaxf(0, fminf(18, floorf(signPow(b / maximumValue, 0.5) * 9 + 9.5))); 117 | 118 | return quantR * 19 * 19 + quantG * 19 + quantB; 119 | } 120 | 121 | static float signPow(float value, float exp) { 122 | return copysignf(powf(fabsf(value), exp), value); 123 | } 124 | 125 | static char characters[83]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"; 126 | 127 | static char *encode_int(int value, int length, char *destination) { 128 | int divisor = 1; 129 | for(int i = 0; i < length - 1; i++) divisor *= 83; 130 | 131 | for(int i = 0; i < length; i++) { 132 | int digit = (value / divisor) % 83; 133 | divisor /= 83; 134 | *destination++ = characters[digit]; 135 | } 136 | return destination; 137 | } 138 | -------------------------------------------------------------------------------- /Website/src/styles/resets.scss: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------------------------------------------------------------------- 2 | 3 | Super Form Reset 4 | 5 | A couple of things to watch out for: 6 | 7 | - IE8: If a text input doesn't have padding on all sides or none the text won't be centered. 8 | - The default border sizes on text inputs in all UAs seem to be slightly different. You're better off using custom borders. 9 | - You NEED to set the font-size and family on all form elements 10 | - Search inputs need to have their appearance reset and the box-sizing set to content-box to match other UAs 11 | - You can style the upload button in webkit using ::-webkit-file-upload-button 12 | - ::-webkit-file-upload-button selectors can't be used in the same selector as normal ones. FF and IE freak out. 13 | - IE: You don't need to fake inline-block with labels and form controls in IE. They function as inline-block. 14 | - By turning off ::-webkit-search-decoration, it removes the extra whitespace on the left on search inputs 15 | 16 | ----------------------------------------------------------------------------------------------------*/ 17 | 18 | input, 19 | label, 20 | select, 21 | button, 22 | textarea { 23 | margin: 0; 24 | border: 0; 25 | padding: 0; 26 | display: inline-block; 27 | vertical-align: middle; 28 | white-space: normal; 29 | background: none; 30 | line-height: 1; 31 | 32 | /* Browsers have different default form fonts */ 33 | font-size: 13px; 34 | font-family: Arial; 35 | } 36 | 37 | /* Remove the stupid outer glow in Webkit */ 38 | input:focus { 39 | outline: 0; 40 | } 41 | 42 | /* Box Sizing Reset 43 | -----------------------------------------------*/ 44 | 45 | /* All of our custom controls should be what we expect them to be */ 46 | input, 47 | textarea { 48 | -webkit-box-sizing: content-box; 49 | -moz-box-sizing: content-box; 50 | box-sizing: content-box; 51 | } 52 | 53 | /* These elements are usually rendered a certain way by the browser */ 54 | button, 55 | input[type='reset'], 56 | input[type='button'], 57 | input[type='submit'], 58 | input[type='checkbox'], 59 | input[type='radio'], 60 | select { 61 | -webkit-box-sizing: border-box; 62 | -moz-box-sizing: border-box; 63 | box-sizing: border-box; 64 | } 65 | 66 | /* Text Inputs 67 | -----------------------------------------------*/ 68 | 69 | input[type='date'], 70 | input[type='datetime'], 71 | input[type='datetime-local'], 72 | input[type='email'], 73 | input[type='month'], 74 | input[type='number'], 75 | input[type='password'], 76 | input[type='range'], 77 | input[type='search'], 78 | input[type='tel'], 79 | input[type='text'], 80 | input[type='time'], 81 | input[type='url'], 82 | input[type='week'] { 83 | } 84 | 85 | /* Button Controls 86 | -----------------------------------------------*/ 87 | 88 | input[type='checkbox'], 89 | input[type='radio'] { 90 | width: 13px; 91 | height: 13px; 92 | } 93 | 94 | /* File Uploads 95 | -----------------------------------------------*/ 96 | 97 | input[type='file'] { 98 | } 99 | 100 | /* Search Input 101 | -----------------------------------------------*/ 102 | 103 | /* Make webkit render the search input like a normal text field */ 104 | input[type='search'] { 105 | -webkit-appearance: textfield; 106 | -webkit-box-sizing: content-box; 107 | } 108 | 109 | /* Turn off the recent search for webkit. It adds about 15px padding on the left */ 110 | ::-webkit-search-decoration { 111 | display: none; 112 | } 113 | 114 | /* Buttons 115 | -----------------------------------------------*/ 116 | 117 | button, 118 | input[type='reset'], 119 | input[type='button'], 120 | input[type='submit'] { 121 | /* Fix IE7 display bug */ 122 | overflow: visible; 123 | width: auto; 124 | } 125 | 126 | /* IE8 and FF freak out if this rule is within another selector */ 127 | ::-webkit-file-upload-button { 128 | padding: 0; 129 | border: 0; 130 | background: none; 131 | } 132 | 133 | /* Textarea 134 | -----------------------------------------------*/ 135 | 136 | textarea { 137 | /* Move the label to the top */ 138 | vertical-align: top; 139 | 140 | /* Turn off scroll bars in IE unless needed */ 141 | overflow: auto; 142 | } 143 | 144 | /* Selects 145 | -----------------------------------------------*/ 146 | 147 | select { 148 | } 149 | 150 | select[multiple] { 151 | /* Move the label to the top */ 152 | vertical-align: top; 153 | } 154 | 155 | /* selected Foundation resets */ 156 | /* copied from node_modules/foundation-sites/scss/_global.scss */ 157 | 158 | html { 159 | box-sizing: border-box; 160 | } 161 | 162 | // Set box-sizing globally to handle padding and border widths 163 | *, 164 | *::before, 165 | *::after { 166 | box-sizing: inherit; 167 | } 168 | 169 | // Default body styles 170 | body { 171 | margin: 0; 172 | padding: 0; 173 | 174 | -webkit-font-smoothing: antialiased; 175 | -moz-osx-font-smoothing: grayscale; 176 | } 177 | 178 | img { 179 | // Get rid of gap under images by making them display: inline-block; by default 180 | display: inline-block; 181 | vertical-align: middle; 182 | 183 | // Grid defaults to get images and embeds to work properly 184 | max-width: 100%; 185 | height: auto; 186 | -ms-interpolation-mode: bicubic; 187 | } 188 | -------------------------------------------------------------------------------- /Swift/BlurHashEncode.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIImage { 4 | public func blurHash(numberOfComponents components: (Int, Int)) -> String? { 5 | guard components.0 >= 1, components.0 <= 9, 6 | components.1 >= 1, components.1 <= 9, 7 | cgImage?.colorSpace?.numberOfComponents == 3, 8 | cgImage?.bitsPerPixel == 24 || cgImage?.bitsPerPixel == 32 else { return nil } 9 | 10 | guard let cgImage = cgImage, 11 | let dataProvider = cgImage.dataProvider, 12 | let data = dataProvider.data, 13 | let pixels = CFDataGetBytePtr(data) else { return nil } 14 | 15 | let width = cgImage.width 16 | let height = cgImage.height 17 | let bytesPerRow = cgImage.bytesPerRow 18 | 19 | var factors: [(Float, Float, Float)] = [] 20 | for y in 0 ..< components.1 { 21 | for x in 0 ..< components.0 { 22 | let normalisation: Float = (x == 0 && y == 0) ? 1 : 2 23 | let factor = multiplyBasisFunction(pixels: pixels, width: width, height: height, bytesPerRow: bytesPerRow, bytesPerPixel: cgImage.bitsPerPixel / 8, pixelOffset: 0) { 24 | normalisation * cos(Float.pi * Float(x) * $0 / Float(width)) * cos(Float.pi * Float(y) * $1 / Float(height)) 25 | } 26 | factors.append(factor) 27 | } 28 | } 29 | 30 | let dc = factors.first! 31 | let ac = factors.dropFirst() 32 | 33 | var hash = "" 34 | 35 | let sizeFlag = (components.0 - 1) + (components.1 - 1) * 9 36 | hash += sizeFlag.encode83(length: 1) 37 | 38 | let maximumValue: Float 39 | if ac.count > 0 { 40 | let actualMaximumValue = ac.map({ max(abs($0.0), abs($0.1), abs($0.2)) }).max()! 41 | let quantisedMaximumValue = Int(max(0, min(82, floor(actualMaximumValue * 166 - 0.5)))) 42 | maximumValue = Float(quantisedMaximumValue + 1) / 166 43 | hash += quantisedMaximumValue.encode83(length: 1) 44 | } else { 45 | maximumValue = 1 46 | hash += 0.encode83(length: 1) 47 | } 48 | 49 | hash += encodeDC(dc).encode83(length: 4) 50 | 51 | for factor in ac { 52 | hash += encodeAC(factor, maximumValue: maximumValue).encode83(length: 2) 53 | } 54 | 55 | return hash 56 | } 57 | 58 | private func multiplyBasisFunction(pixels: UnsafePointer, width: Int, height: Int, bytesPerRow: Int, bytesPerPixel: Int, pixelOffset: Int, basisFunction: (Float, Float) -> Float) -> (Float, Float, Float) { 59 | var r: Float = 0 60 | var g: Float = 0 61 | var b: Float = 0 62 | 63 | let buffer = UnsafeBufferPointer(start: pixels, count: height * bytesPerRow) 64 | 65 | for x in 0 ..< width { 66 | for y in 0 ..< height { 67 | let basis = basisFunction(Float(x), Float(y)) 68 | r += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 0 + y * bytesPerRow]) 69 | g += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 1 + y * bytesPerRow]) 70 | b += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 2 + y * bytesPerRow]) 71 | } 72 | } 73 | 74 | let scale = 1 / Float(width * height) 75 | 76 | return (r * scale, g * scale, b * scale) 77 | } 78 | } 79 | 80 | private func encodeDC(_ value: (Float, Float, Float)) -> Int { 81 | let roundedR = linearTosRGB(value.0) 82 | let roundedG = linearTosRGB(value.1) 83 | let roundedB = linearTosRGB(value.2) 84 | return (roundedR << 16) + (roundedG << 8) + roundedB 85 | } 86 | 87 | private func encodeAC(_ value: (Float, Float, Float), maximumValue: Float) -> Int { 88 | let quantR = Int(max(0, min(18, floor(signPow(value.0 / maximumValue, 0.5) * 9 + 9.5)))) 89 | let quantG = Int(max(0, min(18, floor(signPow(value.1 / maximumValue, 0.5) * 9 + 9.5)))) 90 | let quantB = Int(max(0, min(18, floor(signPow(value.2 / maximumValue, 0.5) * 9 + 9.5)))) 91 | 92 | return quantR * 19 * 19 + quantG * 19 + quantB 93 | } 94 | 95 | private func signPow(_ value: Float, _ exp: Float) -> Float { 96 | return copysign(pow(abs(value), exp), value) 97 | } 98 | 99 | private func linearTosRGB(_ value: Float) -> Int { 100 | let v = max(0, min(1, value)) 101 | if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) } 102 | else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) } 103 | } 104 | 105 | private func sRGBToLinear(_ value: Type) -> Float { 106 | let v = Float(Int64(value)) / 255 107 | if v <= 0.04045 { return v / 12.92 } 108 | else { return pow((v + 0.055) / 1.055, 2.4) } 109 | } 110 | 111 | private let encodeCharacters: [String] = { 112 | return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) } 113 | }() 114 | 115 | extension BinaryInteger { 116 | func encode83(length: Int) -> String { 117 | var result = "" 118 | for i in 1 ... length { 119 | let digit = (Int(self) / pow(83, length - i)) % 83 120 | result += encodeCharacters[Int(digit)] 121 | } 122 | return result 123 | } 124 | } 125 | 126 | private func pow(_ base: Int, _ exponent: Int) -> Int { 127 | return (0 ..< exponent).reduce(1) { value, _ in value * base } 128 | } 129 | -------------------------------------------------------------------------------- /Swift/BlurHashDecode.swift: -------------------------------------------------------------------------------- 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(_ 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) -> 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) -> Substring { 142 | let start = index(startIndex, offsetBy: bounds.lowerBound) 143 | let end = index(startIndex, offsetBy: bounds.upperBound) 144 | return self[start.. UIColor { 113 | let hue = CGFloat(arc4random()) / CGFloat(UInt32.max) 114 | let brightness = CGFloat(arc4random()) / CGFloat(UInt32.max) 115 | if brightness < 0.5 { 116 | return UIColor(hue: hue, saturation: 1, brightness: brightness * 2, alpha: 1) 117 | } else { 118 | return UIColor(hue: hue, saturation: 2 - brightness * 2, brightness: 1, alpha: 1) 119 | } 120 | } 121 | } 122 | 123 | -------------------------------------------------------------------------------- /Kotlin/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /Kotlin/demo/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 172 | -------------------------------------------------------------------------------- /Website/src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | <%= htmlWebpackPlugin.options.title %> 10 | 11 | 12 |
13 |
14 |
15 | 16 | 17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 |
35 |
36 | 37 |
38 |
39 |

40 | BlurHash 41 |

42 |

43 | BlurHash is a compact representation of a placeholder for an image. 44 |

45 | 48 | 49 | More on our blog 50 | 51 |
52 |
53 |
54 |
55 |

Why BlurHash?

56 |

Why would you want this?

57 |

58 | Does your designer cry every time you load their beautifully designed screen, and it is 59 | full of empty boxes because all the images have not loaded yet? Does your database 60 | engineer cry when you want to solve this by trying to cram little thumbnail images into 61 | your data to show as placeholders? 62 |

63 |

BlurHash to the rescue!

64 |

65 | Replace boring grey boxes with beautiful blurhash states and the designers will be happy. 66 | Blurhash strings are short enough to be added as a field in a JSON object and to be stored 67 | in a database. The implementations are small and easy to port to new languages, and the end 68 | result is a smooth and interesting experience for your users. 69 |

70 |
71 |
72 |
73 |

How does it work?

74 |

75 | In short, BlurHash takes an image, and gives you a short string (only 20-30 characters!) 76 | that represents the placeholder for this image. You do this on the backend of your service, 77 | and store the string along with the image. When you send data to your client, you send both 78 | the URL to the image, and the BlurHash string. Your client then takes the string, and 79 | decodes it into an image that it shows while the real image is loading over the network. The 80 | string is short enough that it comfortably fits into whatever data format you use. For 81 | instance, it can easily be added as a field in a JSON object. 82 |

83 |
84 |
85 |
86 | 90 | 94 | 98 | 102 | 103 |
104 |

105 | Pick image or upload your own 106 |

107 | 110 | 111 |
112 | Components 113 |
114 | 115 | x 116 | 117 |
118 |
119 |
120 |
121 |
122 | 123 |
124 |

125 | BlurHash string 126 |

127 |

128 | The blurhash algorithm encodes your image into the short string above, ready to save in a database 129 |

130 |
131 |
132 | 133 |

134 | Result 135 |

136 |

137 | The blurhash string is decoded into a small image that is rendered on to a canvas. 138 |

139 |
140 |
141 |
142 |
143 |

Get started

144 |

145 | The same exported data can be used on all platforms. Get the BlurHash library for your 146 | platform of choice. 147 |

148 | 149 | More on github 150 | 151 |
152 | 153 | 154 | -------------------------------------------------------------------------------- /Website/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o errexit #abort if any command fails 3 | me=$(basename "$0") 4 | 5 | help_message="\ 6 | Usage: $me [-c FILE] [] 7 | Deploy generated files to a git branch. 8 | 9 | Options: 10 | 11 | -h, --help Show this help information. 12 | -v, --verbose Increase verbosity. Useful for debugging. 13 | -e, --allow-empty Allow deployment of an empty directory. 14 | -m, --message MESSAGE Specify the message used when committing on the 15 | deploy branch. 16 | -n, --no-hash Don't append the source commit's hash to the deploy 17 | commit's message. 18 | -c, --config-file PATH Override default & environment variables' values 19 | with those in set in the file at 'PATH'. Must be the 20 | first option specified. 21 | 22 | Variables: 23 | 24 | GIT_DEPLOY_DIR Folder path containing the files to deploy. 25 | GIT_DEPLOY_BRANCH Commit deployable files to this branch. 26 | GIT_DEPLOY_REPO Push the deploy branch to this repository. 27 | 28 | These variables have default values defined in the script. The defaults can be 29 | overridden by environment variables. Any environment variables are overridden 30 | by values set in a '.env' file (if it exists), and in turn by those set in a 31 | file specified by the '--config-file' option." 32 | 33 | parse_args() { 34 | # Set args from a local environment file. 35 | if [ -e ".env" ]; then 36 | source .env 37 | fi 38 | 39 | # Set args from file specified on the command-line. 40 | if [[ $1 = "-c" || $1 = "--config-file" ]]; then 41 | source "$2" 42 | shift 2 43 | fi 44 | 45 | # Parse arg flags 46 | # If something is exposed as an environment variable, set/overwrite it 47 | # here. Otherwise, set/overwrite the internal variable instead. 48 | while : ; do 49 | if [[ $1 = "-h" || $1 = "--help" ]]; then 50 | echo "$help_message" 51 | return 0 52 | elif [[ $1 = "-v" || $1 = "--verbose" ]]; then 53 | verbose=true 54 | shift 55 | elif [[ $1 = "-e" || $1 = "--allow-empty" ]]; then 56 | allow_empty=true 57 | shift 58 | elif [[ ( $1 = "-m" || $1 = "--message" ) && -n $2 ]]; then 59 | commit_message=$2 60 | shift 2 61 | elif [[ $1 = "-n" || $1 = "--no-hash" ]]; then 62 | GIT_DEPLOY_APPEND_HASH=false 63 | shift 64 | else 65 | break 66 | fi 67 | done 68 | 69 | # Set internal option vars from the environment and arg flags. All internal 70 | # vars should be declared here, with sane defaults if applicable. 71 | 72 | # Source directory & target branch. 73 | deploy_directory=${GIT_DEPLOY_DIR:-dist} 74 | deploy_branch=${GIT_DEPLOY_BRANCH:-gh-pages} 75 | 76 | #if no user identity is already set in the current git environment, use this: 77 | default_username=${GIT_DEPLOY_USERNAME:-deploy.sh} 78 | default_email=${GIT_DEPLOY_EMAIL:-} 79 | 80 | #repository to deploy to. must be readable and writable. 81 | repo=${GIT_DEPLOY_REPO:-origin} 82 | 83 | #append commit hash to the end of message by default 84 | append_hash=${GIT_DEPLOY_APPEND_HASH:-true} 85 | } 86 | 87 | main() { 88 | parse_args "$@" 89 | 90 | enable_expanded_output 91 | 92 | if ! git diff --exit-code --quiet --cached; then 93 | echo Aborting due to uncommitted changes in the index >&2 94 | return 1 95 | fi 96 | 97 | commit_title=`git log -n 1 --format="%s" HEAD` 98 | commit_hash=` git log -n 1 --format="%H" HEAD` 99 | 100 | #default commit message uses last title if a custom one is not supplied 101 | if [[ -z $commit_message ]]; then 102 | commit_message="publish: $commit_title" 103 | fi 104 | 105 | #append hash to commit message unless no hash flag was found 106 | if [ $append_hash = true ]; then 107 | commit_message="$commit_message"$'\n\n'"generated from commit $commit_hash" 108 | fi 109 | 110 | previous_branch=`git rev-parse --abbrev-ref HEAD` 111 | 112 | if [ ! -d "$deploy_directory" ]; then 113 | echo "Deploy directory '$deploy_directory' does not exist. Aborting." >&2 114 | return 1 115 | fi 116 | 117 | # must use short form of flag in ls for compatibility with OS X and BSD 118 | if [[ -z `ls -A "$deploy_directory" 2> /dev/null` && -z $allow_empty ]]; then 119 | echo "Deploy directory '$deploy_directory' is empty. Aborting. If you're sure you want to deploy an empty tree, use the --allow-empty / -e flag." >&2 120 | return 1 121 | fi 122 | 123 | if git ls-remote --exit-code $repo "refs/heads/$deploy_branch" ; then 124 | # deploy_branch exists in $repo; make sure we have the latest version 125 | 126 | disable_expanded_output 127 | git fetch --force $repo $deploy_branch:$deploy_branch 128 | enable_expanded_output 129 | fi 130 | 131 | # check if deploy_branch exists locally 132 | if git show-ref --verify --quiet "refs/heads/$deploy_branch" 133 | then incremental_deploy 134 | else initial_deploy 135 | fi 136 | 137 | restore_head 138 | } 139 | 140 | initial_deploy() { 141 | git --work-tree "$deploy_directory" checkout --orphan $deploy_branch 142 | git --work-tree "$deploy_directory" add --all 143 | commit+push 144 | } 145 | 146 | incremental_deploy() { 147 | #make deploy_branch the current branch 148 | git symbolic-ref HEAD refs/heads/$deploy_branch 149 | #put the previously committed contents of deploy_branch into the index 150 | git --work-tree "$deploy_directory" reset --mixed --quiet 151 | git --work-tree "$deploy_directory" add --all 152 | 153 | set +o errexit 154 | diff=$(git --work-tree "$deploy_directory" diff --exit-code --quiet HEAD --)$? 155 | set -o errexit 156 | case $diff in 157 | 0) echo No changes to files in $deploy_directory. Skipping commit.;; 158 | 1) commit+push;; 159 | *) 160 | echo git diff exited with code $diff. Aborting. Staying on branch $deploy_branch so you can debug. To switch back to master, use: git symbolic-ref HEAD refs/heads/master && git reset --mixed >&2 161 | return $diff 162 | ;; 163 | esac 164 | } 165 | 166 | commit+push() { 167 | set_user_id 168 | git --work-tree "$deploy_directory" commit -m "$commit_message" 169 | 170 | disable_expanded_output 171 | #--quiet is important here to avoid outputting the repo URL, which may contain a secret token 172 | git push --quiet $repo $deploy_branch 173 | enable_expanded_output 174 | } 175 | 176 | #echo expanded commands as they are executed (for debugging) 177 | enable_expanded_output() { 178 | if [ $verbose ]; then 179 | set -o xtrace 180 | set +o verbose 181 | fi 182 | } 183 | 184 | #this is used to avoid outputting the repo URL, which may contain a secret token 185 | disable_expanded_output() { 186 | if [ $verbose ]; then 187 | set +o xtrace 188 | set -o verbose 189 | fi 190 | } 191 | 192 | set_user_id() { 193 | if [[ -z `git config user.name` ]]; then 194 | git config user.name "$default_username" 195 | fi 196 | if [[ -z `git config user.email` ]]; then 197 | git config user.email "$default_email" 198 | fi 199 | } 200 | 201 | restore_head() { 202 | if [[ $previous_branch = "HEAD" ]]; then 203 | #we weren't on any branch before, so just set HEAD back to the commit it was on 204 | git update-ref --no-deref HEAD $commit_hash $deploy_branch 205 | else 206 | git symbolic-ref HEAD refs/heads/$previous_branch 207 | fi 208 | 209 | git reset --mixed 210 | } 211 | 212 | filter() { 213 | sed -e "s|$repo|\$repo|g" 214 | } 215 | 216 | sanitize() { 217 | "$@" 2> >(filter 1>&2) | filter 218 | } 219 | 220 | [[ $1 = --source-only ]] || main "$@" -------------------------------------------------------------------------------- /Website/src/styles/averta.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * MyFonts Webfont Build ID 3433766, 2017-08-08T04:46:45-0400 4 | * 5 | * The fonts listed in this notice are subject to the End User License 6 | * Agreement(s) entered into by the website owner. All other parties are 7 | * explicitly restricted from using the Licensed Webfonts(s). 8 | * 9 | * You may obtain a valid license at the URLs below. 10 | * 11 | * Webfont: AvertaStdPE-Black by Intelligent Design 12 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/black-177285/ 13 | * 14 | * Webfont: AvertaStdPE-BlackItalic by Intelligent Design 15 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/black-ital-177285/ 16 | * 17 | * Webfont: AvertaStdPE-Bold by Intelligent Design 18 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/bold-177285/ 19 | * 20 | * Webfont: AvertaStdPE-Extrabold by Intelligent Design 21 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/exbold-177285/ 22 | * 23 | * Webfont: AvertaStdPE-BoldItalic by Intelligent Design 24 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/bold-ital-177285/ 25 | * 26 | * Webfont: AvertaStdPE-ExtraboldItalic by Intelligent Design 27 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/exbold-ital-177285/ 28 | * 29 | * Webfont: AvertaStdPE-Extrathin by Intelligent Design 30 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/pe-exthin/ 31 | * 32 | * Webfont: AvertaStdPE-ExtrathinItalic by Intelligent Design 33 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/exthin-italic-177285/ 34 | * 35 | * Webfont: AvertaStdPE-Light by Intelligent Design 36 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/light-177285/ 37 | * 38 | * Webfont: AvertaStdPE-LightItalic by Intelligent Design 39 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/light-ital-177285/ 40 | * 41 | * Webfont: AvertaStdPE-Regular by Intelligent Design 42 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/regular-177285/ 43 | * 44 | * Webfont: AvertaStdPE-RegularItalic by Intelligent Design 45 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/italic-177285/ 46 | * 47 | * Webfont: AvertaStdPE-Semibold by Intelligent Design 48 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/semibold-177285/ 49 | * 50 | * Webfont: AvertaStdPE-SemiboldItalic by Intelligent Design 51 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/semibold-ital-177285/ 52 | * 53 | * Webfont: AvertaStdPE-Thin by Intelligent Design 54 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/thin-177285/ 55 | * 56 | * Webfont: AvertaStdPE-ThinItalic by Intelligent Design 57 | * URL: https://www.myfonts.com/fonts/intelligent-foundry/averta-standard/thin-italic-177285/ 58 | * 59 | * 60 | * License: https://www.myfonts.com/viewlicense?type=web&buildid=3433766 61 | * Licensed pageviews: 10,000 62 | * Webfonts copyright: Copyright (c) 2015 by Kostas Bartsokas. All rights reserved. 63 | * 64 | * © 2017 MyFonts Inc 65 | */ 66 | 67 | /* @import must be at top of file, otherwise CSS will not work */ 68 | @import url('//hello.myfonts.net/count/346526'); 69 | 70 | @font-face { 71 | font-family: 'AvertaStdPE-Black'; 72 | src: url('../assets/fonts/averta/346526_0_0.eot'); 73 | src: url('../assets/fonts/averta/346526_0_0.eot?#iefix') format('embedded-opentype'), 74 | url('../assets/fonts/averta/346526_0_0.woff2') format('woff2'), 75 | url('../assets/fonts/averta/346526_0_0.woff') format('woff'), 76 | url('../assets/fonts/averta/346526_0_0.ttf') format('truetype'); 77 | } 78 | 79 | @font-face { 80 | font-family: 'AvertaStdPE-BlackItalic'; 81 | src: url('../assets/fonts/averta/346526_1_0.eot'); 82 | src: url('../assets/fonts/averta/346526_1_0.eot?#iefix') format('embedded-opentype'), 83 | url('../assets/fonts/averta/346526_1_0.woff2') format('woff2'), 84 | url('../assets/fonts/averta/346526_1_0.woff') format('woff'), 85 | url('../assets/fonts/averta/346526_1_0.ttf') format('truetype'); 86 | } 87 | 88 | @font-face { 89 | font-family: 'AvertaStdPE-Bold'; 90 | src: url('../assets/fonts/averta/346526_2_0.eot'); 91 | src: url('../assets/fonts/averta/346526_2_0.eot?#iefix') format('embedded-opentype'), 92 | url('../assets/fonts/averta/346526_2_0.woff2') format('woff2'), 93 | url('../assets/fonts/averta/346526_2_0.woff') format('woff'), 94 | url('../assets/fonts/averta/346526_2_0.ttf') format('truetype'); 95 | } 96 | 97 | @font-face { 98 | font-family: 'AvertaStdPE-Extrabold'; 99 | src: url('../assets/fonts/averta/346526_3_0.eot'); 100 | src: url('../assets/fonts/averta/346526_3_0.eot?#iefix') format('embedded-opentype'), 101 | url('../assets/fonts/averta/346526_3_0.woff2') format('woff2'), 102 | url('../assets/fonts/averta/346526_3_0.woff') format('woff'), 103 | url('../assets/fonts/averta/346526_3_0.ttf') format('truetype'); 104 | } 105 | 106 | @font-face { 107 | font-family: 'AvertaStdPE-BoldItalic'; 108 | src: url('../assets/fonts/averta/346526_4_0.eot'); 109 | src: url('../assets/fonts/averta/346526_4_0.eot?#iefix') format('embedded-opentype'), 110 | url('../assets/fonts/averta/346526_4_0.woff2') format('woff2'), 111 | url('../assets/fonts/averta/346526_4_0.woff') format('woff'), 112 | url('../assets/fonts/averta/346526_4_0.ttf') format('truetype'); 113 | } 114 | 115 | @font-face { 116 | font-family: 'AvertaStdPE-ExtraboldItalic'; 117 | src: url('../assets/fonts/averta/346526_5_0.eot'); 118 | src: url('../assets/fonts/averta/346526_5_0.eot?#iefix') format('embedded-opentype'), 119 | url('../assets/fonts/averta/346526_5_0.woff2') format('woff2'), 120 | url('../assets/fonts/averta/346526_5_0.woff') format('woff'), 121 | url('../assets/fonts/averta/346526_5_0.ttf') format('truetype'); 122 | } 123 | 124 | @font-face { 125 | font-family: 'AvertaStdPE-Extrathin'; 126 | src: url('../assets/fonts/averta/346526_6_0.eot'); 127 | src: url('../assets/fonts/averta/346526_6_0.eot?#iefix') format('embedded-opentype'), 128 | url('../assets/fonts/averta/346526_6_0.woff2') format('woff2'), 129 | url('../assets/fonts/averta/346526_6_0.woff') format('woff'), 130 | url('../assets/fonts/averta/346526_6_0.ttf') format('truetype'); 131 | } 132 | 133 | @font-face { 134 | font-family: 'AvertaStdPE-ExtrathinItalic'; 135 | src: url('../assets/fonts/averta/346526_7_0.eot'); 136 | src: url('../assets/fonts/averta/346526_7_0.eot?#iefix') format('embedded-opentype'), 137 | url('../assets/fonts/averta/346526_7_0.woff2') format('woff2'), 138 | url('../assets/fonts/averta/346526_7_0.woff') format('woff'), 139 | url('../assets/fonts/averta/346526_7_0.ttf') format('truetype'); 140 | } 141 | 142 | @font-face { 143 | font-family: 'AvertaStdPE-Light'; 144 | src: url('../assets/fonts/averta/346526_8_0.eot'); 145 | src: url('../assets/fonts/averta/346526_8_0.eot?#iefix') format('embedded-opentype'), 146 | url('../assets/fonts/averta/346526_8_0.woff2') format('woff2'), 147 | url('../assets/fonts/averta/346526_8_0.woff') format('woff'), 148 | url('../assets/fonts/averta/346526_8_0.ttf') format('truetype'); 149 | } 150 | 151 | @font-face { 152 | font-family: 'AvertaStdPE-LightItalic'; 153 | src: url('../assets/fonts/averta/346526_9_0.eot'); 154 | src: url('../assets/fonts/averta/346526_9_0.eot?#iefix') format('embedded-opentype'), 155 | url('../assets/fonts/averta/346526_9_0.woff2') format('woff2'), 156 | url('../assets/fonts/averta/346526_9_0.woff') format('woff'), 157 | url('../assets/fonts/averta/346526_9_0.ttf') format('truetype'); 158 | } 159 | 160 | @font-face { 161 | font-family: 'AvertaStdPE-Regular'; 162 | src: url('../assets/fonts/averta/346526_A_0.eot'); 163 | src: url('../assets/fonts/averta/346526_A_0.eot?#iefix') format('embedded-opentype'), 164 | url('../assets/fonts/averta/346526_A_0.woff2') format('woff2'), 165 | url('../assets/fonts/averta/346526_A_0.woff') format('woff'), 166 | url('../assets/fonts/averta/346526_A_0.ttf') format('truetype'); 167 | } 168 | 169 | @font-face { 170 | font-family: 'AvertaStdPE-RegularItalic'; 171 | src: url('../assets/fonts/averta/346526_B_0.eot'); 172 | src: url('../assets/fonts/averta/346526_B_0.eot?#iefix') format('embedded-opentype'), 173 | url('../assets/fonts/averta/346526_B_0.woff2') format('woff2'), 174 | url('../assets/fonts/averta/346526_B_0.woff') format('woff'), 175 | url('../assets/fonts/averta/346526_B_0.ttf') format('truetype'); 176 | } 177 | 178 | @font-face { 179 | font-family: 'AvertaStdPE-Semibold'; 180 | src: url('../assets/fonts/averta/346526_C_0.eot'); 181 | src: url('../assets/fonts/averta/346526_C_0.eot?#iefix') format('embedded-opentype'), 182 | url('../assets/fonts/averta/346526_C_0.woff2') format('woff2'), 183 | url('../assets/fonts/averta/346526_C_0.woff') format('woff'), 184 | url('../assets/fonts/averta/346526_C_0.ttf') format('truetype'); 185 | } 186 | 187 | @font-face { 188 | font-family: 'AvertaStdPE-SemiboldItalic'; 189 | src: url('../assets/fonts/averta/346526_D_0.eot'); 190 | src: url('../assets/fonts/averta/346526_D_0.eot?#iefix') format('embedded-opentype'), 191 | url('../assets/fonts/averta/346526_D_0.woff2') format('woff2'), 192 | url('../assets/fonts/averta/346526_D_0.woff') format('woff'), 193 | url('../assets/fonts/averta/346526_D_0.ttf') format('truetype'); 194 | } 195 | 196 | @font-face { 197 | font-family: 'AvertaStdPE-Thin'; 198 | src: url('../assets/fonts/averta/346526_E_0.eot'); 199 | src: url('../assets/fonts/averta/346526_E_0.eot?#iefix') format('embedded-opentype'), 200 | url('../assets/fonts/averta/346526_E_0.woff2') format('woff2'), 201 | url('../assets/fonts/averta/346526_E_0.woff') format('woff'), 202 | url('../assets/fonts/averta/346526_E_0.ttf') format('truetype'); 203 | } 204 | 205 | @font-face { 206 | font-family: 'AvertaStdPE-ThinItalic'; 207 | src: url('../assets/fonts/averta/346526_F_0.eot'); 208 | src: url('../assets/fonts/averta/346526_F_0.eot?#iefix') format('embedded-opentype'), 209 | url('../assets/fonts/averta/346526_F_0.woff2') format('woff2'), 210 | url('../assets/fonts/averta/346526_F_0.woff') format('woff'), 211 | url('../assets/fonts/averta/346526_F_0.ttf') format('truetype'); 212 | } 213 | -------------------------------------------------------------------------------- /Website/src/index.scss: -------------------------------------------------------------------------------- 1 | @import 'styles/utils.scss'; 2 | @import 'styles/resets.scss'; 3 | @import 'styles/variables.scss'; 4 | @import 'styles/base.scss'; 5 | @import 'styles/averta.scss'; 6 | 7 | @mixin img($top, $left) { 8 | position: absolute; 9 | top: top($top); 10 | left: left($left); 11 | 12 | & > .image-canvas-bg { 13 | position: absolute; 14 | z-index: -1; 15 | top: 0; 16 | width: 100%; 17 | height: 100%; 18 | } 19 | 20 | & > .image-bg { 21 | will-change: opacity; 22 | opacity: 1; 23 | } 24 | } 25 | 26 | h2 { 27 | font-family: Helvetica; 28 | font-weight: 400; 29 | font-size: 60px; 30 | line-height: 70px; 31 | 32 | @media screen and (max-width: 600px) { 33 | font-size: 40px; 34 | line-height: 50px; 35 | } 36 | } 37 | 38 | .button { 39 | border-radius: 4px; 40 | padding: 1rem; 41 | min-width: 10rem; 42 | color: #fff; 43 | font-weight: 500; 44 | background-color: $wolt-id-blue; 45 | 46 | &:hover, 47 | &:focus { 48 | outline: none; 49 | background-color: mix(#fff, $wolt-id-blue, 24); 50 | } 51 | 52 | &:active { 53 | background-color: mix($dark-grey, $wolt-id-blue, 12); 54 | } 55 | } 56 | 57 | .hero { 58 | height: 100vh; 59 | 60 | .header { 61 | height: $header-height; 62 | background-color: #fff; 63 | display: flex; 64 | align-items: center; 65 | justify-content: center; 66 | } 67 | 68 | .button { 69 | margin-bottom: 2.5rem; 70 | } 71 | 72 | .logo { 73 | z-index: 1; 74 | } 75 | 76 | .content { 77 | z-index: 1; 78 | height: 100%; 79 | display: flex; 80 | justify-content: center; 81 | align-items: center; 82 | flex-direction: column; 83 | opacity: 0; 84 | } 85 | 86 | .title { 87 | font-family: AvertaStdPE-Semibold; 88 | font-size: 6.6rem; 89 | margin: 0; 90 | margin-bottom: 2rem; 91 | 92 | @media screen and (max-width: 600px) { 93 | font-size: 4rem; 94 | } 95 | } 96 | 97 | .description { 98 | margin: 0 2rem 3.5rem; 99 | font-size: 1.5rem; 100 | max-width: 700px; 101 | text-align: center; 102 | } 103 | 104 | .link { 105 | position: relative; 106 | font-weight: 500; 107 | text-decoration: none; 108 | line-height: 1.8; 109 | &::after { 110 | position: absolute; 111 | bottom: 0; 112 | left: 0; 113 | width: 100%; 114 | content: ''; 115 | border: 1px solid $dark-grey; 116 | border-bottom: 0; 117 | } 118 | } 119 | 120 | .title, 121 | .description, 122 | .link { 123 | background-image: radial-gradient(rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0)); 124 | } 125 | 126 | .imagesContainer { 127 | position: absolute; 128 | z-index: 0; 129 | width: 100%; 130 | height: 100%; 131 | will-change: transform; 132 | } 133 | 134 | .img1 { 135 | @include img(55, 919); 136 | } 137 | .img2 { 138 | @include img(169, 216); 139 | } 140 | .img3 { 141 | @include img(338, 1162); 142 | } 143 | .img4 { 144 | @include img(482, 1038); 145 | } 146 | .img5 { 147 | @include img(551, 235); 148 | } 149 | } 150 | 151 | .animateImages { 152 | animation: revealBlurhash 5s 2.5s infinite; 153 | } 154 | 155 | @keyframes revealBlurhash { 156 | 0% { 157 | opacity: 1; 158 | } 159 | 160 | 25%, 161 | 50% { 162 | opacity: 0; 163 | } 164 | 165 | 75%, 166 | 100% { 167 | opacity: 1; 168 | } 169 | } 170 | 171 | .section { 172 | position: relative; 173 | background: #fff; 174 | display: flex; 175 | flex-direction: column; 176 | } 177 | 178 | @keyframes cross-fade { 179 | 0% { opacity: 0 } 180 | 10% { opacity: 1 } 181 | 50% { opacity: 1 } 182 | 60% { opacity: 0 } 183 | } 184 | 185 | .why-blurhash { 186 | position: relative; 187 | padding-top: 24px; 188 | padding-bottom: 50px; 189 | padding-left: 10vw; 190 | display: flex; 191 | min-height: 800px; 192 | 193 | &:before, &:after { 194 | content: ''; 195 | height: 731px; 196 | width: 365px; 197 | position: absolute; 198 | top: 75px; 199 | right: 75%; 200 | background-size: 100% 100%; 201 | } 202 | 203 | &:before { 204 | background-image: url(../assets/images/Bad_screen@2x.png); 205 | } 206 | 207 | &:after { 208 | background-image: url(../assets/images/Good_screen@2x.png); 209 | opacity: 0; 210 | animation: cross-fade 5s infinite; 211 | } 212 | 213 | 214 | @media screen and (min-width: 600px) { 215 | padding-left: 30vw; 216 | 217 | &:before, &:after { 218 | right: 60%; 219 | } 220 | } 221 | 222 | .content { 223 | background: rgba(229, 238, 255, 0.37); 224 | height: 100%; 225 | flex: 1; 226 | display: flex; 227 | flex-direction: column; 228 | align-items: flex-start; 229 | justify-content: center; 230 | 231 | font-size: 16px; 232 | line-height: 22px; 233 | padding: 0 24px 24px 20vw; 234 | 235 | @media screen and (min-width: 600px) { 236 | padding: 0 20vw 50px; 237 | } 238 | 239 | p { 240 | max-width: 590px; 241 | } 242 | } 243 | 244 | h3 { 245 | font-size: 20px; 246 | font-weight: 700; 247 | letter-spacing: 0.34px; 248 | } 249 | } 250 | 251 | .how-it-works { 252 | text-align: center; 253 | 254 | p { 255 | font-family: Helvetica; 256 | font-size: 28px; 257 | letter-spacing: -0.5px; 258 | max-width: 870px; 259 | margin: 0 auto; 260 | 261 | @media screen and (max-width: 600px) { 262 | font-size: 20px; 263 | padding: 0 24px; 264 | } 265 | } 266 | } 267 | 268 | .demo { 269 | max-width: 1200px; 270 | margin: 0 auto; 271 | padding: 100px 10px 130px; 272 | display: flex; 273 | justify-content: stretch; 274 | 275 | @media screen and (max-width: 600px) { 276 | flex-direction: column; 277 | padding: 24px; 278 | } 279 | 280 | .part { 281 | position: relative; 282 | flex: 1; 283 | margin: 0 24px; 284 | 285 | &:first-child { 286 | margin-left: 0; 287 | } 288 | &:last-child { 289 | margin-right: 0; 290 | 291 | &:after { 292 | content: unset; 293 | } 294 | } 295 | 296 | @media screen and (max-width: 600px) { 297 | margin: 24px 0; 298 | } 299 | 300 | &:after { 301 | content: ''; 302 | background: url('../assets/images/arrow.svg'); 303 | width: 72px; 304 | height: 25px; 305 | position: absolute; 306 | top: -32px; 307 | right: -63px; 308 | 309 | @media screen and (max-width: 600px) { 310 | top: unset; 311 | right: unset; 312 | bottom: -45px; 313 | left: calc(50% - 32px); 314 | transform: rotate(90deg); 315 | z-index: 1; 316 | } 317 | } 318 | } 319 | 320 | .explain { 321 | margin: 24px 0; 322 | font-size: 18px; 323 | text-align: center; 324 | line-height: 24px; 325 | } 326 | 327 | .explain-small { 328 | font-size: 14px; 329 | color: #838383; 330 | letter-spacing: -0.15px; 331 | text-align: center; 332 | line-height: 18px; 333 | } 334 | 335 | .predefined { 336 | position: relative; 337 | display: flex; 338 | justify-content: space-between; 339 | flex-direction: row; 340 | flex-wrap: wrap; 341 | height: 200px; 342 | 343 | label { 344 | position: relative; 345 | width: 45%; 346 | height: 90px; 347 | margin-bottom: 5%; 348 | cursor: pointer; 349 | background-size: cover; 350 | 351 | input { 352 | position: absolute; 353 | top: 16px; 354 | left: 16px; 355 | } 356 | 357 | &:nth-of-type(1) { 358 | background-image: url(../assets/images/img1.jpg); 359 | } 360 | &:nth-of-type(2) { 361 | background-image: url(../assets/images/img2.jpg); 362 | } 363 | &:nth-of-type(3) { 364 | background-image: url(../assets/images/img3.jpg); 365 | } 366 | &:nth-of-type(4) { 367 | background-image: url(../assets/images/img4.jpg); 368 | } 369 | 370 | > img { 371 | display: none; 372 | } 373 | } 374 | 375 | #original-canvas { 376 | position: absolute; 377 | top: 0; 378 | left: 0; 379 | width: 100%; 380 | height: 100%; 381 | pointer-events: none; 382 | opacity: 0; 383 | transition: opacity 0.2s ease-in-out; 384 | background: #eee; 385 | 386 | &.visible { 387 | pointer-events: unset; 388 | opacity: 1; 389 | cursor: pointer; 390 | } 391 | } 392 | } 393 | 394 | #file-upload { 395 | display: none; 396 | } 397 | 398 | .dimension { 399 | margin-top: 24px; 400 | font-size: 14px; 401 | color: #868789; 402 | letter-spacing: -0.15px; 403 | text-align: center; 404 | line-height: 20px; 405 | 406 | > div { 407 | margin-top: 12px; 408 | } 409 | 410 | input { 411 | background: #ffffff; 412 | border: 1px solid rgba(32, 33, 37, 0.12); 413 | border-radius: 4px; 414 | text-align: center; 415 | width: 46px; 416 | height: 40px; 417 | } 418 | } 419 | 420 | .demo-blurhash { 421 | position: relative; 422 | box-shadow: 0 4px 20px rgba(32, 33, 37, 0.2); 423 | height: 200px; 424 | padding: 24px; 425 | background: #ffffff; 426 | border-radius: 2px; 427 | font-size: 18px; 428 | text-align: center; 429 | line-height: 24px; 430 | display: flex; 431 | justify-content: center; 432 | align-items: center; 433 | 434 | &:focus-within { 435 | box-shadow: 0 4px 20px rgba(32, 33, 37, 0.4); 436 | } 437 | } 438 | 439 | #demo-blurhash { 440 | color: $wolt-id-blue; 441 | word-break: break-all; 442 | 443 | &.error { 444 | color: $error-color-id; 445 | } 446 | } 447 | 448 | #demo-canvas { 449 | height: 200px; 450 | width: 100%; 451 | background: #ddd; 452 | } 453 | } 454 | 455 | .get-started { 456 | background: url(../assets/images/get-started-bg.jpg); 457 | max-width: 1180px; 458 | height: 556px; 459 | margin: 0 auto; 460 | display: flex; 461 | align-items: center; 462 | justify-content: center; 463 | text-align: center; 464 | 465 | h2 { 466 | margin: 0; 467 | } 468 | 469 | h2, 470 | p { 471 | color: #fff; 472 | } 473 | 474 | p { 475 | font-size: 24px; 476 | line-height: 32px; 477 | padding: 0 20%; 478 | 479 | @media screen and (max-width: 600px) { 480 | padding: 0 24px; 481 | } 482 | } 483 | 484 | .white-button { 485 | background: #fff; 486 | padding: 1rem 2rem; 487 | color: $wolt-id-blue; 488 | text-decoration: none; 489 | } 490 | } 491 | --------------------------------------------------------------------------------