If Anyone Interested in Java decoder For Android App(Derived from Demo Kotlin library)
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Build;
import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class BlurHashDecoder {
// cache Math.cos() calculations to improve performance.
// The number of calculations can be huge for many bitmaps: width * height * numCompX * numCompY * 2 * nBitmaps
// the cache is enabled by default, it is recommended to disable it only when just a few images are displayed
private final HashMap<Integer, double[]> cacheCosinesX = new HashMap<>();
private final HashMap<Integer, double[]> cacheCosinesY = new HashMap<>();
private static Map<Character, Integer> charMap = new HashMap();
private static final BlurHashDecoder INSTANCE = new BlurHashDecoder();
public static BlurHashDecoder getInstance() {
return INSTANCE;
}
private BlurHashDecoder() {
}
static {
Character[] characters = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z', '#', '$', '%', '*', '+', ',', '-', '.', ':', ';', '=', '?', '@', '[', ']', '^', '_', '{', '|', '}', '~'
};
for (int i = 0; i < characters.length; i++) {
charMap.put(characters[i], i);
}
}
/**
* Clear calculations stored in memory cache.
* The cache is not big, but will increase when many image sizes are used,
* if the app needs memory it is recommended to clear it.
*/
private void clearCache() {
cacheCosinesX.clear();
cacheCosinesY.clear();
}
/**
* Decode a blur hash into a new bitmap.
*
* @param useCache use in memory cache for the calculated math, reused by images with same size.
* if the cache does not exist yet it will be created and populated with new calculations.
* By default it is true.
*/
public Bitmap decode(@Nullable String blurHash, int width, int height, float punch, boolean useCache) {
if (blurHash == null || blurHash.length() <= 6) {
return null;
}
int numCompEnc = decode83(blurHash, 0, 1);
int numCompX = numCompEnc % 9 + 1;
int numCompY = numCompEnc / 9 + 1;
if (blurHash.length() != 4 + 2 * numCompX * numCompY) {
return null;
} else {
int maxAcEnc = this.decode83(blurHash, 1, 2);
float maxAc = (float)(maxAcEnc + 1) / 166.0F;
float[][] colors = new float[numCompX * numCompY][];
for(int i = 0; i < numCompX * numCompY; ++i) {
if (i == 0) {
int colorEnc = decode83(blurHash, 2, 6);
colors [i] = decodeDc(colorEnc);
} else {
int from = 4 + i * 2;
int colorEnc = decode83(blurHash, from, from + 2);
colors [i] = decodeAc(colorEnc, maxAc * punch);
}
}
return composeBitmap(width, height, numCompX, numCompY, colors, useCache);
}
}
private int decode83(String str, int from, int to) {
int result = 0;
for (int i = from; i < to; i++) {
int index = charMap.get(str.charAt(i));
if (index != -1) {
result = result * 83 + index;
}
}
return result;
}
private float[] decodeDc(int colorEnc) {
int r = colorEnc >> 16;
int g = colorEnc >> 8 & 255;
int b = colorEnc & 255;
return new float[]{sRGBToLinear(r), sRGBToLinear(g), sRGBToLinear(b)};
}
private float sRGBToLinear(double colorEnc) {
float v = (float)colorEnc / 255.0F;
if (v <= 0.04045F) {
return v / 12.92F;
} else {
return (float)Math.pow((v + 0.055F) / 1.055F, 2.4F);
}
}
private float[] decodeAc(int value, float maxAc) {
int r = value / 361;
int g = (value / 19) % 19;
int b = value % 19;
return new float[]{
signedPow2((r - 9) / 9.0F) * maxAc,
signedPow2((g - 9) / 9.0F) * maxAc,
signedPow2((b - 9) / 9.0F) * maxAc
};
}
private float signedPow2(float value) {
return Math.copySign((float)Math.pow((double)value, (double)2.0F), value);
}
private Bitmap composeBitmap(int width, int height, int numCompX, int numCompY, float[][] colors , boolean useCache) {
// use an array for better performance when writing pixel colors
int[] imageArray = new int[width * height];
boolean calculateCosX = !useCache || !cacheCosinesX.containsKey(width * numCompX);
double[] cosinesX = getArrayForCosinesX(calculateCosX, width, numCompX);
boolean calculateCosY = !useCache || !cacheCosinesY.containsKey(height * numCompY);
double[] cosinesY = getArrayForCosinesY(calculateCosY, height, numCompY);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float r = 0.0F;
float g = 0.0F;
float b = 0.0F;
for (int j = 0; j < numCompY; j++) {
for (int i = 0; i < numCompX; i++) {
double cosX = getCos(cosinesX, calculateCosX, i, numCompX, x, width);
double cosY = getCos(cosinesY, calculateCosY, j, numCompY, y, height);
float basis = (float)(cosX * cosY);
float[] color = colors[j * numCompX + i];
r += color[0] * basis;
g += color[1] * basis;
b += color[2] * basis;
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
imageArray[x + width * y] = Color.rgb(linearToSRGB(r), linearToSRGB(g), linearToSRGB(b));
} else {
imageArray[x + width * y] = Color.argb(255, linearToSRGB(r), linearToSRGB(g), linearToSRGB(b));
}
}
}
return Bitmap.createBitmap(imageArray, width, height, Bitmap.Config.ARGB_8888);
}
private double[] getArrayForCosinesY(boolean calculate, int height, int numCompY) {
if (calculate) {
double[] cosinesY = new double[height * numCompY];
cacheCosinesY.put(height * numCompY, cosinesY);
return cosinesY;
} else {
return (double[]) cacheCosinesY.get(height * numCompY);
}
}
private double[] getArrayForCosinesX(boolean calculate, int width, int numCompX) {
if (calculate) {
double[] cosinesX = new double[width * numCompX];
cacheCosinesX.put(width * numCompX, cosinesX);
return cosinesX;
} else {
return (double[]) cacheCosinesX.get(width * numCompX);
}
}
private double getCos(double[] getCos, boolean calculate, int x, int numComp, int y, int size) {
if (calculate) {
getCos[x + numComp * y] = Math.cos(Math.PI * y * x / size);
}
return getCos[x + numComp * y];
}
private int linearToSRGB(double value) {
double v = Math.max(0, Math.min(1, value));
if (v <= 0.0031308F) {
return (int) (v * 12.92F * 255.0F + 0.5F);
} else {
return (int) ((1.055F * Math.pow(v, (1 / 2.4F)) - 0.055F) * 255 + 0.5F);
}
}
}
Thanks a LOT for this!!
importar android.graphics.Bitmap;reboke*** importar android.graphics.Color; > importar android.os.Build; > > importar androidx.annotation.Nullable; > > importar java.util.HashMap; > importar java.util.Map; > > clase pública BlurHashDecoder { > > // cache Math.cos() cálculos para mejorar el rendimiento. > // El número de cálculos puede ser enorme para muchos mapas de bits: ancho * alto * numCompX * numCompY * 2 * nBitmaps > // la caché está habilitada de forma predeterminada, se recomienda deshabilitarla solo cuando se muestren solo unas pocas imágenes > private final HashMap<Entero, double[]> cacheCosinesX = new HashMap<>(); > final privado HashMap<Entero, doble[]> cacheCosinesY = nuevo HashMap<>(); > Mapa estático privado<Personaje, Entero> charMap = nuevo HashMap(); > > instancia final estática privada de BlurHashDecoder = nuevo BlurHashDecoder(); > > public static BlurHashDecoder getInstance() { > devolver INSTANCIA; > } > > private BlurHashDecoder() { > } > > estático { > Carácter[] caracteres = { > '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J' > 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l' > 'u', 'v', 'w', 'x', 'y', 'z', '#', '$', '%', '*', '+',', '-', '.', ':', ';', '=', '?', '@', '[', ' > }; > para (int i = 0; i < characters.length; i++) { > charMap.put(personajes[i], i); > } > } > > /** > * Borrar los cálculos almacenados en la caché de memoria. > * La caché no es grande, pero aumentará cuando se utilicen muchos tamaños de imagen, > * si la aplicación necesita memoria, se recomienda borrarla. > / > private void clearCache() { > cacheCosinesX.clear(); > cacheCosinesY.clear(); > } > > /* > * Decodifica un hash de desenfoque en un nuevo mapa de bits. > * > * @param useCache use en la caché de memoria para las matemáticas calculadas, reutilizadas por imágenes del mismo tamaño. > * si la caché aún no existe, se creará y rellenará con nuevos cálculos. > * Por defecto es cierto. > */ > public Bitmap decode(@Nullable String blurHash, int width, int height, float punch, boolean useCache) { > if (blurHash == null || blurHash.length() <= 6) { > devolver null; > } > > int numCompEnc = decode83(blurHash, 0, 1); > int numCompX = numCompEnc % 9 + 1; > int numCompY = numCompEnc / 9 + 1; > if (blurHash.length() ! = 4 + 2 * numCompX * numCompY) { > devolver null; > } else { > int maxAcEnc = this.decode83(blurHash, 1, 2); > float maxAc = (flotante)(maxAcEnc + 1) / 166.0F; > float[][] colors = new float[numCompX * numCompY][]; > > for(int i = 0; i < numCompX * numCompY; ++i) { > si (i == 0) { > int colorEnc = decode83(blurHash, 2, 6); > colores [i] = decodeDc(colorEnc); > } de lo contrario { > int from = 4 + i * 2; > int colorEnc = decode83(blurHash, from, from + 2); > colores [i] = decodeAc(colorEnc, maxAc * punch); > } > } > devolver composeBitmap(ancho, alto, numCompX, numCompY, colores, useCache); > } > } > > private int decode83(String str, int from, int to) { > resultado int = 0; > para (int i = from; i < to; i++) { > índice int = charMap.get(str.charAt(i)); > if (índice! = -1) { > resultado = resultado * 83 + índice; > } > } > resultado de retorno; > } > > > flotador privado[] decodeDc(int colorEnc) { > int r = colorEnc >> 16; > int g = colorEnc >> 8 y 255; > int b = colorEnc & 255; > devolver nuevo flotador[]{sRGBToLinear(r), sRGBToLinear(g), sRGBToLinear(b)}; > } > > flotador privado sRGBToLinear(double colorEnc) { > float v = (flotante)colorEnc / 255.0F; > si (v <= 0,04045F) { > return v / 12.92F; > } de lo contrario { > return (float)Math.pow((v + 0.055F) / 1.055F, 2.4F); > } > } > > float privado[] decodeAc(int value, float maxAc) { > int r = valor / 361; > int g = (valor / 19) % 19; > int b = valor % 19; > return new float[]{ > signedPow2((r - 9) / 9.0F) * maxAc, > signedPow2((g - 9) / 9.0F) * maxAc, > signedPow2((b - 9) / 9.0F) * maxAc > }; > } > > flotador privado signedPow2(valor flotante) { > devuelve Math.copySign((float)Math.pow((double)value, (double)2.0F), value); > } > > mapa de bits privado composeBitmap(int width, int height, int numCompX, int numCompY, float[][] colors , boolean useCache) { > // usar una matriz para un mejor rendimiento al escribir colores de píxeles > int[] imageArray = new int[width * height]; > booleano calculateCosX = ! useCache || ! cacheCosinesX.containsKey(ancho * numCompX); > double[] cosinesX = getArrayForCosinesX(calcularCosX, ancho, numCompX); > booleano calculateCosY = ! useCache || ! cacheCosinesY.containsKey(altura * numCompY); > double[] cosinesY = getArrayForCosinesY(calcularCosY, altura, numCompY); > para (int y = 0; y < altura; y++) { > para (int x = 0; x < ancho; x++) { > flotador r = 0,0F; > flotador g = 0,0F; > flotador b = 0,0F; > para (int j = 0; j < numCompY; j++) { > para (int i = 0; i < numCompX; i++) { > doble cosX = getCos(cosinesX, calculateCosX, i, numCompX, x, ancho); > doble cosY = getCos(cosinesY, calculateCosY, j, numCompY, y, altura); > base flotante = (flotante) (cosX * cosY); > float[] color = colores[j * numCompX + i]; > r += color[0] * base; > g += color[1] * base; > b += color[2] * base; > } > } > > if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { > imageArray[x + width * y] = Color.rgb(linearToSRGB(r), linearToSRGB(g), linearToSRGB(b)); > } de lo contrario { > imageArray[x + width * y] = Color.argb(255, linearToSRGB(r), linearToSRGB(g), linearToSRGB(b)); > } > } > } > devolver Bitmap.createBitmap(imageArray, width, height, Bitmap.Config.ARGB_8888); > } > > doble privado[] getArrayForCosinesY(boolean calculate, int height, int numCompY) { > si (calcular) { > double[] cosinesY = nuevo double[altura * numCompY]; > cacheCosinesY.put(altura * numCompY, cosinesY); > cosinesY de devolución; > } de lo contrario { > devolver (doble[]) cacheCosinesY.get(altura * numCompY); > } > } > > doble privado[] getArrayForCosinesX(boolean calculate, int width, int numCompX) { > si (calcular) { > doble[] cosinesX = nuevo doble[ancho * numCompX]; > cacheCosinesX.put(ancho * numCompX, cosinesX); > devolver cosinesX; > } de lo contrario { > devolver (doble[]) cacheCosinesX.get(width * numCompX); > } > } > > private double getCos(double[] getCos, boolean calculate, int x, int numComp, int y, int size) { > si (calcular) { > getCos[x + numComp * y] = Math.cos(Math.PI * y * x / tamaño); > } > devuelve getCos[x + numComp * y]; > } > > private int linearToSRGB(doble valor) { > double v = Math.max(0, Math.min(1, valor)); > si (v <= 0.0031308F) { > retorno (int) (v * 12.92F * 255.0F + 0.5F); > } de lo contrario { > return (int) ((1.055F * Math.pow(v, (1 / 2.4F)) - 0.055F) * 255 + 0.5F); > } > } > } R
Feel free to create a repo with the implementation - then we can link it in our README. Closing.