Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

module4jitpack-io #148

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
46 changes: 46 additions & 0 deletions Kotlin/blurhashkt-lib/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
plugins {
id 'com.android.library'
id 'kotlin-android'
id 'kotlin-kapt'
id 'maven-publish'
}

android {

compileSdkVersion 31

defaultConfig {
minSdkVersion 14
targetSdkVersion 31
versionCode 2
versionName "1.0.$versionCode"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

}

dependencies {
androidTestImplementation "junit:junit:4.13.2"
}


afterEvaluate {
publishing {
publications {
release(MavenPublication) {
from components.release
groupId = 'com.github.wolt'
artifactId = 'blurhash-lib'
version = '1.0.2'
}
}
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.wolt.blurhashkt

import android.graphics.Bitmap
import java.lang.Math.*
import java.nio.IntBuffer
import kotlin.math.PI
import kotlin.math.pow

object BlurHashEncoder {

fun blurHash(bitmap: Bitmap, components: Pair<Int, Int>): String? {
if (components.first !in 1..9 || components.second !in 1..9) {
return null
}


val width = bitmap.width
val height = bitmap.height
val bytesPerRow = bitmap.rowBytes
var pixels: IntArray? = null
bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)

if (pixels == null) {
return null
}

val factors: MutableList<Array<Float>> = mutableListOf()
for (y in 0 until components.second) {
for (x in 0 until components.first) {
val normalisation: Float = if (x == 0 && y == 0) 1f else 2f
val factor =
multiplyBasisFunction(pixels, width, height, bytesPerRow, 4, 0) { a, b ->
(normalisation * kotlin.math.cos(PI * x * a / width) * kotlin.math.cos(PI * y * b / height)).toFloat()
}
factors.add(factor)
}
}

val dc = factors.removeAt(0)
val ac = factors

var hash = ""

val sizeFlag = (components.first - 1) + (components.second - 1) * 9
hash += sizeFlag.encode83(1)

val maximumValue: Float
if (ac.size > 0) {
val actualMaximumValue = ac.map { it.maxOrNull() }.maxByOrNull { it!! }!!
val quantisedMaximumValue =
0.0.coerceAtLeast(82.0.coerceAtMost(kotlin.math.floor(actualMaximumValue * 166 - 0.5)))
.toInt()
maximumValue = (quantisedMaximumValue + 1) / 166.0f
hash += quantisedMaximumValue.encode83(1)
} else {
maximumValue = 1f
hash += 0.encode83(1)
}

hash += encodeDC(dc).encode83(4)

for (factor in ac) {
hash += encodeAC(factor, maximumValue).encode83(2)
}

return hash
}

private fun multiplyBasisFunction(
pixels: IntArray,
width: Int,
height: Int,
bytesPerRow: Int,
bytesPerPixel: Int,
pixelOffset: Int,
basisFunction: (Float, Float) -> Float
): Array<Float> {
var r = 0f
var g = 0f
var b = 0f

val buffer = IntBuffer.wrap(pixels, pixels.size, height * bytesPerRow)

for (x in 0 until width) {
for (y in 0 until height) {
val basis = basisFunction(x.toFloat(), y.toFloat())
r += basis * sRgbToLinear(buffer[bytesPerPixel * x + pixelOffset + 0 + y * bytesPerRow])
g += basis * sRgbToLinear(buffer[bytesPerPixel * x + pixelOffset + 1 + y * bytesPerRow])
b += basis * sRgbToLinear(buffer[bytesPerPixel * x + pixelOffset + 2 + y * bytesPerRow])
}
}

val scale = 1 / (width * height).toFloat()

return arrayOf(r * scale, g * scale, b * scale)
}


private val encodeCharacters: List<String> =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { it.toString() }

private fun encodeDC(value: Array<Float>): Int {
val roundedR = linearToSRgb(value[0])
val roundedG = linearToSRgb(value[1])
val roundedB = linearToSRgb(value[2])
return (roundedR shl 16) + (roundedG shl 8) + roundedB
}

private fun encodeAC(value: Array<Float>, maximumValue: Float): Int {
0.0.coerceAtLeast(
18.0.coerceAtMost(
kotlin.math.floor(
(value[0] / maximumValue.toDouble()).pow(0.5) * 9 + 9.5
)
)
)
val quantR = 0.0.coerceAtLeast(
18.0.coerceAtMost(
kotlin.math.floor(
(value[0] / maximumValue.toDouble()).pow(0.5) * 9 + 9.5
)
)
).toInt()
val quantG = 0.0.coerceAtLeast(
18.0.coerceAtMost(
kotlin.math.floor(
(value[1] / maximumValue.toDouble()).pow(0.5) * 9 + 9.5
)
)
).toInt()
val quantB = 0.0.coerceAtLeast(
18.0.coerceAtMost(
kotlin.math.floor(
(value[2] / maximumValue.toDouble()).pow(0.5) * 9 + 9.5
)
)
).toInt()

return quantR * 19 * 19 + quantG * 19 + quantB
}

private fun sRgbToLinear(value: Int): Float {
val v = value / 255f
return if (v <= 0.04045) (v / 12.92f) else (pow((v + 0.055) / 1.055, 2.4).toFloat())
}

private fun linearToSRgb(value: Float): Int {
val v = 0f.coerceAtLeast(1f.coerceAtMost(value))
return if (v <= 0.0031308f) {
(v * 12.92f * 255 + 0.5f).toInt()
} else {
((1.055f * v.toDouble().pow(1 / 2.4) - 0.055f) * 255 + 0.5f).toInt()
}
}


private fun Int.encode83(length: Int): String {
var result = ""
for (i in 1..length) {
val digit = (this / myPow(83, (length - i))) % 83
result += encodeCharacters[digit]
}
return result
}

private fun myPow(base: Int, exponent: Int): Int {
return (0 until exponent).fold(1) { acc, _ -> acc * base }
}
}
4 changes: 2 additions & 2 deletions Kotlin/build.gradle
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@

buildscript {

ext.kotlin_version = '1.3.72'
ext.kotlin_version = '1.5.31'

repositories {
google()
jcenter()
}

dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:7.0.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}

Expand Down
25 changes: 17 additions & 8 deletions Kotlin/demo/build.gradle
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'maven-publish'
}

android {

compileSdkVersion 29
compileSdkVersion 31

defaultConfig {
applicationId "com.wolt.blurhash"
minSdkVersion 14
targetSdkVersion 29
versionCode 1
targetSdkVersion 31
versionCode 2
versionName "1.0"
}

buildFeatures {
dataBinding true
}


buildTypes {
release {
minifyEnabled false
Expand All @@ -24,6 +32,7 @@ android {
}

dependencies {
implementation project(path: ':lib')
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation project(path: ':blurhashkt-lib')
implementation "androidx.appcompat:appcompat:1.3.1"
}

10 changes: 6 additions & 4 deletions Kotlin/demo/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wolt.blurhashapp">
package="com.wolt.blurhashapp">

<application
android:allowBackup="true"
Expand All @@ -9,11 +9,13 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.wolt.blurhashapp.MainActivity">
<activity
android:name="com.wolt.blurhashapp.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
Expand Down
18 changes: 12 additions & 6 deletions Kotlin/demo/src/main/java/com/wolt/blurhashapp/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,27 @@ import android.graphics.Bitmap
import android.os.Bundle
import android.os.SystemClock
import androidx.appcompat.app.AppCompatActivity
import com.wolt.blurhashapp.databinding.ActivityMainBinding
import com.wolt.blurhashkt.BlurHashDecoder
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvDecode.setOnClickListener {

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

binding.tvDecode.setOnClickListener {
var bitmap: Bitmap? = null
val time = timed {
bitmap = BlurHashDecoder.decode(etInput.text.toString(), 20, 12)
bitmap = BlurHashDecoder.decode(binding.etInput.text.toString(), 20, 12)
}
ivResult.setImageBitmap(bitmap)
ivResultTime.text = "Time: $time ms"
binding.ivResult.setImageBitmap(bitmap)
binding.ivResultTime.text = "Time: $time ms"
}
}

Expand Down
Loading