Giter Site home page Giter Site logo

woltapp / blurhash Goto Github PK

View Code? Open in Web Editor NEW
15.0K 71.0 334.0 11.95 MB

A very compact representation of a placeholder for an image.

Home Page: https://blurha.sh

License: MIT License

Makefile 0.08% C 74.02% Kotlin 2.45% Swift 10.27% HTML 0.37% TypeScript 2.54% JavaScript 1.79% Shell 1.44% SCSS 5.52% EJS 1.54%

blurhash's Introduction

BlurHash is a compact representation of a placeholder for an image.

Why would you want this?

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

BlurHash will solve your problems! How? Like this:

You can also see nice examples and try it out yourself at blurha.sh!

How does it work?

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

In summary:

   

Want to know all the gory technical details? Read the algorithm description.

Implementing the algorithm is actually quite easy! Implementations are short and easily ported to your favourite language or platform.

Implementations

So far, we have created these implementations:

  • C - An encoder implementation in portable C code.
  • Swift - Encoder and decoder implementations, and a larger library offering advanced features. There is also an example app to play around with the algorithm.
  • Kotlin - A decoder implementation for Android.
  • TypeScript - Encoder and decoder implementations, and an example page to test.
  • Python - Integration of the C encoder code into Python.

These cover our use cases, but could probably use polishing, extending and improving. There are also these third party implementations that we know of:

  • Pure Python - Implementation of both the encoder and decoder in pure Python.
  • One version in Go, and another version in Go.
  • PHP - Encoder and decoder implementations in pure PHP.
  • Java - Encoder implementation in Java.
  • Clojure - Encoder and decoder implementations in Clojure.
  • Nim - Encoder and decoder implementation in pure Nim.
  • Rust and WebAssembly - Encoder and decoder implementations in Rust. Distributed as both native Rust and WebAssembly packages.
  • Ruby - Encoder implementation in Ruby.
  • Crystal - Encoder implementation in pure Crystal.
  • Elm - Encoder and decoder in Elm.
  • Dart - Encoder and decoder implementation in C into Dart using dart-ffi.
  • Pure Dart - Encoder and decoder implementation in pure Dart.
  • .NET - Encoder and decoder in C#.
  • JavaScript - Encoder and decoder implementation in pure JavaScript.
  • .NET - Encoder implementation in C#.
  • Haskell - Encoder and decoder in pure Haskell.
  • Scala - Encoder and decoder in Scala.
  • Elixir - Encoder implementation in pure Elixir.
  • ReScript - Encoder and decoder implementation in ReScript (BuckleScript).
  • JavaScript - Tiny optimized decoder implementation JS.
  • Xojo - Encoder and decoder implementation in pure Xojo.
  • React Native - UI Component for React Native. (Decoder in Swift and Kotlin)
  • Zig - Encoder implementation in Zig.
  • Titanium SDK - Decoder for Titanium SDK (Android)
  • BQN - Encoder, decoder and terminal viewer in pure BQN.
  • Jetpack Compose - Decoder Jetpack Compose implementation
  • C++ - Encoder and decoder in C++.
  • Kotlin Multiplatform - Encoding & decoding for Android, iOS & JVM
  • OCaml - Encoder implementation in OCaml.

Can't find the language you're looking for? Try your luck with the GitHub search. For example, here are the search results for repos which have "blurhash" in their name.

Perhaps you'd like to help extend this list? Which brings us to...

Contributing

We'd love contributions! The algorithm is very simple - less than two hundred lines of code - and can easily be ported to your platform of choice. And having support for more platforms would be wonderful! So, Java decoder? Golang encoder? Haskell? Rust? We want them all!

We will also try to tag any issues on our issue tracker that we'd love help with, so if you just want to dip in, go have a look.

You can file a pull request with us, or you can start your own repo and project if you want to run everything yourself, we don't mind.

If you do want to contribute to this project, we have a code of conduct.

Users

Who uses BlurHash? Here are some projects we know about:

  • Wolt - We are of course using it ourselves. BlurHashes are used in the mobile clients on iOS and Android, as well as on the web, as placeholders during image loading.
  • Mastodon - The Mastodon decentralised social media network uses BlurHashes both as loading placeholders, as well as for hiding media marked as sensitive.
  • Signal - Signal Private Messenger uses Blurhashes as placeholders before photo & video messages are downloaded in chat conversations.
  • Jellyfin - Jellyfin the free software media system uses Blurhashes as placeholders for images of movies and TV shows when they are being downloaded.

Good Questions

How fast is encoding? Decoding?

These implementations are not very optimised. Running them on very large images can be a bit slow. The performance of the encoder and decoder are about the same for the same input or output size, so decoding very large placeholders, especially on your UI thread, can also be a bit slow.

However! The trick to using the algorithm efficiently is to not run it on full-sized data. The fine detail of an image is all thrown away, so you should scale your images down before running BlurHash on them. If you are creating thumbnails, run BlurHash on those instead of the full images.

Similarly, when displaying the placeholders, very small images work very well when scaled up. We usually decode placeholders that are 32 or even 20 pixels wide, and then let the UI layer scale them up, which is indistinguishable from decoding them at full size.

How do I pick the number of X and Y components?

It depends a bit on taste. The more components you pick, the more information is retained in the placeholder, but the longer the BlurHash string will be. Also, it doesn't always look good with too many components. We usually go with 4 by 3, which seems to strike a nice balance.

However, you should adjust the number of components depending on the aspect ratio of your images. For instance, very wide images should have more X components and fewer Y components.

The Swift example project contains a test app where you can play around with the parameters and see the results.

What is the punch parameter in some of these implementations?

It is a parameter that adjusts the contrast on the decoded image. 1 means normal, smaller values will make the effect more subtle, and larger values will make it stronger. This is basically a design parameter, which lets you adjust the look.

Technically, what it does is scale the AC components up or down.

Is this only useful as an image loading placeholder?

Well, that is what it was designed for originally, but it turns out to be useful for a few other things:

  • Masking images without having to use expensive blurs - Mastodon uses it for this.
  • The data representation makes it quite easy to extract colour averages of the image for different areas. You can easily find approximations of things like the average colour of the top edge of the image, or of a corner. There is some code in the Swift BlurHashKit implementation to experiment with this. Also, the average colour of the entire image is just the DC component and can be decoded even without implementing any of the more complicated DCT stuff.
  • We have been meaning to try to implement tinted drop shadows for UI elements by using the BlurHash and extending the borders. Haven't actually had time to implement this yet though.

Why base 83?

First, 83 seems to be about how many low-ASCII characters you can find that are safe for use in all of JSON, HTML and shells.

Secondly, 83 * 83 is very close to, and a little more than, 19 * 19 * 19, making it ideal for encoding three AC components in two characters.

What about using the full Unicode character set to get a more efficient encoding?

We haven't looked into how much overhead UTF-8 encoding would introduce versus base 83 in single-byte characters, but the encoding and decoding would probably be a lot more complicated, so in the spirit of minimalism BlurHash uses the simpler option. It might also be awkward to copy-paste, depending on OS capabilities.

If you think it can be done and is worth it, though, do make your own version and show us! We'd love to see it in action.

What about other basis representations than DCT?

This is something we'd love to try. The DCT looks quite ugly when you increase the number of components, probably because the shape of the basis functions becomes too visible. Using a different basis with more aesthetically pleasing shape might be a big win.

However, we have not managed come up with one. Some experimenting with a Fourier-Bessel base, targeted at images that are going to be cropped into circles has been done, but without much success. Here again we'd love to see what you can come up with!

Authors

blurhash's People

Contributors

0xflotus avatar connormiha avatar connyduck avatar dagagren avatar dependabot[bot] avatar dhikshith12 avatar freitzzz avatar hangduykhiem avatar ignaciotcrespo avatar jerry-git avatar lauri-kaariainen avatar lautat avatar leonklingele avatar lorendb avatar lorenzck avatar m1ga avatar markussammallahti avatar markyc avatar mhoward540 avatar mrousavy avatar narasimha1997 avatar nygardk avatar omahlama avatar perzanko avatar serushakov avatar thisen avatar vanniktech avatar vluoto avatar wajahat-iqbal avatar werehamster avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blurhash's Issues

CSS or SVG blur image

Would be nice to generate a CSS gradient or a SVG image to pass as object's background via CSS.

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);
        }
    }
}

memory leak on big size image

I have 10 MB image so when i use PHP implementation, 12 GB of memory get max and kills the process

thsi is class

class BlurHashHelper
{
    public static function encode($file_path)
    {
        $image = imagecreatefromstring(file_get_contents($file_path));
        $width = imagesx($image);
        $height = imagesy($image);
    
        $pixels = [];
        for ($y = 0; $y < $height; ++$y) {
            $row = [];
            for ($x = 0; $x < $width; ++$x) {
                $index = imagecolorat($image, $x, $y);
                $colors = imagecolorsforindex($image, $index);
    
                $row[] = [$colors['red'], $colors['green'], $colors['blue']];
            }
            $pixels[] = $row;
        }
    
        $components_x = 4;
        $components_y = 2;
        $blurhash = Blurhash::encode($pixels, $components_x, $components_y);
        $pixels = [];
        return $blurhash;
    }
}

Account for transparent pixels in .PNGs

I'm working on an iOS app that has a ton of images with transparent pixels. As of right now, it seems like any pixels that are transparent are treated as if they were black.

basketball-removebg-preview
becomes
IMG_1320

It may be that I'm calling it incorrectly, but I didn't see anything in the docs about this.

Swift decoder is really slow!

The Swift decoder is really slow compared to the Android (Kotlin) decoder.

Here are some benchmarks I made while creating the mrousavy/react-native-blurhash library:

Blurhash Size iOS Android
32 x 32 8 ms 2 ms
400 x 400 1.134 ms 130 ms
2000 x 2000 28.894ms 3.336ms

Of course you would never need such high decode widths/heights, this is just for benchmarking purposes. But even with 32x32 the iOS decoder is 4 times slower than the Android one! This doesn't play out nicely in large lists, as two blurhashes already cause a framedrop (16ms = 1/60 fps).

After a lot of debugging I'm still not quite sure where the bottleneck is in the Swift decoder, does anyone have an idea/solution?

Decoding

Is there a way to decode it to data uri? like data:image/png;base64,(decoded blurhash). So it'd be able to use on different platforms by returning that representation from my API

Horrible showcase

Recorded on iPhone 11 Pro

IMG_6089.MP4

Why all the flickering? I hope this isn't a concern I should have about this?

Add link to pure JS implementation repo to readme

Hey, I really like the project!

I couldn't find a vanilla JS version, only the TypeScript one, so I ported the TypeScript implementation to vanilla JS and added a helper function here and there.

The repo: https://github.com/Dens49/blurhash-js

If my repo holds up to your standards, you might want to consider adding a link to it to your Readme. I'm sure there are more people looking for a vanilla JS version.

That's a very good idea. :D

That's a very good idea. :D

defmodule Blurhash do
  @characters "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~"
  @comp_x 6
  @comp_y 6
  @width 20
  @height 20

  def hash(url) do
    url = URI.encode(url)
    case HTTPoison.get(url) do
      {:ok, %HTTPoison.Response{body: body}} ->
        body
        |> get_rgb_data(@width, @height)
        |> generate_blurhash(@comp_x, @comp_y, @width, @height)

      _ -> "error"
    end
  end

  defp get_rgb_data(body, width, height) do
    {:ok, path} = Temp.path(suffix: ".jpg")
    File.write(path, body)

    %Mogrify.Image{path: rgbpath} =
      Mogrify.open(path)
      |> Mogrify.resize("#{width}x#{height}!")
      |> Mogrify.format("rgb")
      |> Mogrify.save()

    data =
      File.read!(rgbpath)
      |> :binary.bin_to_list()

    File.rm!(path)
    File.rm!(rgbpath)

    data
  end

  defp generate_blurhash(data, xc, yc, width, height) do
    [dc | ac] = get_factors(0, 0, xc, yc, width, height, data)

    qmx = quantised_max_value_of(ac)
    mx = (qmx + 1.0) / 166.0

    [
      encode_value((xc - 1) + (yc - 1) * 9, 1),
      encode_value(qmx, 1),
      encode_dc(dc)
      |
      encode_acs(ac, mx)
    ]
    |> Enum.join()
  end

  defp get_factors(_, yc, _, yc, _, _, _), do: []
  defp get_factors(xc, y, xc, yc, w, h, d), do: get_factors(0, y + 1, xc, yc, w, h, d)
  defp get_factors(x, y, xc, yc, width, height, data) do
    [
      multiply_basis_function(x, y, width, height, data)
      |
      get_factors(x + 1, y, xc, yc, width, height, data)
    ]
  end

  defp multiply_basis_function(x, y, w, h, data) do
    pi_x = :math.pi() * x / w
    pi_y = :math.pi() * y / h
    calc_rgb(0, 0, w, h, pi_x, pi_y, data)
    |> scale(x, y, w, h)
  end

  defp calc_rgb(_, h, _, h, _, _, _), do: {0, 0, 0}
  defp calc_rgb(w, y, w, h, pi_x, pi_y, data), do: calc_rgb(0, y + 1, w, h, pi_x, pi_y, data)
  defp calc_rgb(x, y, w, h, pi_x, pi_y, data) do
    {r, g, b} = calc_rgb(x + 1, y, w, h, pi_x, pi_y, data)

    basis = :math.cos(pi_x * x) * :math.cos(pi_y * y)
    position = 3 * (x + y * w)

    {
      r + basis * s_rgb_to_linear(Enum.at(data, 0 + position)),
      g + basis * s_rgb_to_linear(Enum.at(data, 1 + position)),
      b + basis * s_rgb_to_linear(Enum.at(data, 2 + position)),
    }
  end

  defp scale({r, g, b}, x, y, w, h) do
    scl = normalisation(x, y) / (w * h)
    {r * scl, g * scl, b * scl}
  end

  defp normalisation(0, 0), do: 1
  defp normalisation(_, _), do: 2

  defp encode_dc({r, g, b}) do
    r = linear_to_s_rgb(r)
    g = linear_to_s_rgb(g)
    b = linear_to_s_rgb(b)

    encode_value((r * 0x10000) + (g * 0x100) + b, 4)
  end

  defp encode_acs([], _), do: []
  defp encode_acs([ac | remaining_acs], mx) do
    [encode_ac(ac, mx) | encode_acs(remaining_acs, mx)]
  end

  defp encode_ac({r, g, b}, mx) do
    quant_r = quant(r / mx)
    quant_g = quant(g / mx)
    quant_b = quant(b / mx)

    encode_value((quant_r * 19 * 19) + (quant_g * 19) + quant_b, 2)
  end

  defp quant(value) do
    value
    |> sign_pow(0.5)
    |> fn v -> v * 9 + 9.5 end.()
    |> Kernel.trunc()
    |> min(18)
    |> max(0)
  end

  defp sign_pow(v, exp) do
    av = abs(v)
    :math.pow(av, exp) * (v / av)
  end

  defp quantised_max_value_of(ac) do
    Enum.map(ac, fn {r, g, b} -> [abs(r), abs(g), abs(b)] end)
    |> List.flatten()
    |> Enum.max()
    |> fn x -> Kernel.trunc(x * 166 - 0.5) end.()
    |> min(82)
    |> max(0)
  end

  defp s_rgb_to_linear(value) do
    v = value / 255.0
    if v <= 0.04045 do
      v / 12.92
    else
      :math.pow((v + 0.055) / 1.055, 2.4)
    end
  end

  defp linear_to_s_rgb(value) do
    v = max(0, min(1, value))
    if v <= 0.0031308 do
      v * 12.92 * 255 + 0.5
    else
      (1.055 * :math.pow(v, 1 / 2.4) - 0.055) * 255 + 0.5
    end
  end

  defp encode_value(_, 0), do: ""
  defp encode_value(value, length) do
    value = Kernel.trunc(value)
    encode_value(div(value, 83), length - 1) <> String.at(@characters, rem(value, 83))
  end
end

TBH the blurhashes this produces are a bit lurid, so I'm guessing I've messed up the maths somewhere along the line. But I'm pretty sure that's doing what the C and Dart implementations (which I've used as reference) are doing.

Originally posted by @shinyford in #44 (comment)

Create packages for easy installation

Hi, it's a nice library.
However, it seems that it's not currently available as the package for Swift Package Manager or via CocoaPods, so that the library could be easily integrated with the application target.
Supporting both would serve multiple purposes:

  • Makes it easier to integrate with the applications
  • Keeps the library code separate from the app code
  • Makes it very easy to upgrade to new versions of BlurHash
  • Assists discovery

I recommend creating a library definition files (.podspec and package.swift), so that the library could be integrated with the dependency management tools.

Elixir implementation of encoder

Hi

I'm trying to port the blurhash encoder to Elixir (happy to share when it works) and seem to be getting somewhere. However, it's noticeably slow, which is unusual for Elixir to say the least. (Also tried to implement in Dart/Flutter, and again it appears slow.)

Your Javascript (?) implementation on https://blurha.sh looks like it's pretty much instantaneous. So my question is: am I possibly doing something stupid, or is there something special happening on the website?

Many thanks for this BTW!

Cheers

Nic

TypeScript implementation encodes non-zero DC component for all-black images

From the README:

Also, the average colour of the entire image is just the DC component

However, when you try a completely black picture on the homepage (all pixels are 0), the DC component will be 09jvf, so not 0, and not the average color of the image. Not that it matters much for rendering the blurred version (it still looks kind of all black), but it seems to indicate that something is off.

Note that this works correctly in the Python implementation, and I think the key difference is that the Python one simply cuts off the fractional part in its linearTosRGB instead of rounding the return value.

As a side note, it would be great if all the magic numbers and numerics in the reference implementation would come with a bit of inline documentation so it's easier to understand what they are for. :)

Documentation / usage?

I couldn't find any documentation for the TypeScript usage. Do I really have to look through the demo/ directory and look at the code? Is there any plan to have a wiki or anything similar? Great project btw! :>

Web component

Would be great to have plain webcomponent to make blurhash usable with whatever frontend technology/framework and also independent from the server side encosing technology/language.

Usage would be similar to https://github.com/woltapp/react-blurhash but implemented in plain web technology

Random hash

Can I have random hash not from image?

It can be useful from null or default case.

Webpack plugin

Hi all,

What do you think about Webpack plugin that inlines the base64 image till the real image is loaded?

C Implementation doesn't produce "best looking" blurhash

I have just compared the blurhash TypeScript module and the C module (I was about to write a blurhash node module based on the C implementation for performance).
The two blurhashes from the modules look different:

C Implementation TS implementation (better)
Screenshot 2021-01-13 at 08 40 33 Screenshot 2021-01-13 at 08 39 23
Has a dark spot in the top left corner matches the image really well
LTEfiu4UEe-p0M-oxuRQnhtkRQR* UEE:Ct005i~C00^%%MIUr;%yMyNG^R9uRP%M

The original image used, with x: 4, y: 3 components:
The original image used

Can someone explain the difference? Shouldn't the both modules create (nearly) the same output?
I think that the result from the TS implementation looks better and the result from the C module should match this.

Errors with sourcemap while transpiling

Hi there, what a nice project!

...I'm using Parcel Bundler to build my frontend javascript code, with the npm package from your repository. While compiling, I get these error messages:

⚠️  Could not load source file "../src/index.ts" in source map of "../node_modules/blurhash/dist/index.js".
⚠️  Could not load source file "../src/decode.ts" in source map of "../node_modules/blurhash/dist/decode.js".
⚠️  Could not load source file "../src/error.ts" in source map of "../node_modules/blurhash/dist/error.js".
⚠️  Could not load source file "../src/encode.ts" in source map of "../node_modules/blurhash/dist/encode.js".
⚠️  Could not load source file "../src/base83.ts" in source map of "../node_modules/blurhash/dist/base83.js".
⚠️  Could not load source file "../src/utils.ts" in source map of "../node_modules/blurhash/dist/utils.js".

This is how I import the modules in my script:

import { decode as decodeBlurhash, isBlurhashValid } from "blurhash";

Any Idea?

nodejs implementation

I would try this algorithm but how to convert/use Uint8ClampedArray type in nodejs service.
Is it possible to have a more generic algorithm using only "basic" types ?

It seems that Uint8ClampedArray is an array of 8-bit unsigned integers clamped to 0-255

Which implementation of BlurHash is most suitable for embeding in to Google Cloud Functions for encoding an Image?

Thanks for this game changer algorithm/tool.

I need to use back-end encoding since there are no encoding libraries for dart/flutter yet. And people recommend against encoding in mobile client.

I use google back-end and after some investigation I found out that Python code can be run from Google Cloud Functions.

There is a Pure Python implementation and there is one which is not pure Python(It uses C code). Are these two compatible with GCF?

If not, which language implemantation can we embed in GCF? Typescript? Javascript? C? etc..

I want to make a HTTP request function in Cloud Functions which will run one of these implementation libraries and encode an Image and return a blurhash string.

Thanks

src from local directory not working

Hi, I am having an issue with displaying an image from my local directory:

<img data-v-6383fb07="" src="~/assets/images/postcards/jake-starr-f.png" alt="green lawn grass during daytime" style="height: 100%; width: 100%; position: absolute; top: 0px; left: 0px; right: 0px; bottom: 0px;">

This fails to load the image, however loading an external image works fine, eg:

<img data-v-6383fb07="" src="https://image.nuxtjs.org/preview.png" alt="green lawn grass during daytime" style="height: 100%; width: 100%; position: absolute; top: 0px; left: 0px; right: 0px; bottom: 0px;">

This is most likely not an error with blur hash, but if someone can figure out what I'm doing wrong it'd be much appreciated! Thanks in advance

Make the base 83 encoding optional.

Suppose I want to store 80,000 pre-blurred images in my database. LjIY5?00?bIUofWBWBM{WBofWBj[ is a sample 4×3 blurHash, and is 28 bytes (224 bits) long. Suppose I use a fixed-width data type in my database, so I don't need the first character of the string which encodes the length. If we remove the first character and encode the same blurHash in its equivalent raw bytes, we need only ~10 bytes (73 bits) to encode the hash, saving 1.5 MB on disk for 80,000 images. That may seem like a micro-optimization, especially in an RDBMS which may duplicate data in indexes. You could say I'm just allergic to plaintext formats when a more compact representation would do, and you'd be right :) I still think there's value in this approach though.

React Native implementation

UPDATE: I've created a native UI module for iOS (Swift) and Android (Kotlin) myself, check it out here: mrousavy/react-native-blurhash

Hi! The library is really awesome, but I can't seem to find a way to use it in React Native (with the blurhash-typescript library).
The decode(...) function returns a raw pixel array (UInt8ClampedArray), but the <Image> component can't be initialized with a pixel array. (see: react-native Image.source)

It takes really long in typescript

  • for quad HD image blurhash generation takes about 30 seconds

I don't know if it's just my specific problem, but I have four larger images (6000 x 4000), I convert them to a max of 2560px on one side, and it takes about two minutes to generate a 6 x 6 blurhash for all of them. If I convert them to a max of 256px on one side, it takes almost no time at all, which makes sense, but the two minutes seems too much even for such a large image. Maybe would help some downscaling before generating blurhash.

Publish Kotlin library to Maven Central

We've started using BlurHashes for Jellyfin starting from version 10.6. I'm one of the maintainers of the Android TV app and I'm currently reviewing a pull request that adds support for BlurHash placeholders while loading images. So far it works great and I'm excited to ship the feature soon. Unfortunately the contributor had to copy the Kotlin decoder to our repository because it doesn't seem to be published anywhere.

This brings me to my request: would it be possible to publish the Kotlin decoder to Maven Central, so we can easily include it without copying the sources to our own repository?

Reference images and hashes for testing

It would be good to have a canonical set of reference images (like the ones included in the testing projects), with a matching set of reference hashes for various component sizes (X and Y). This would greatly help with testing and verifying new implementations.

I'm currently implementing a C# port of BlurHash (BlurSharp), and am in the process of writing tests for the encode function. Using the colour Doughnut image pic2.png from the python testing project, I get differing results from the current implementations:

https://blurha.sh/: LSM=%_00?^H?OpV]R.E1R*X9R%bH
blurhash-python: LlMF%n00%#MwS|WCWEM{R*bbWBbH
BlurSharp: LlF?k*00?ZE2kQaejFM{WAocWBj?

My result is obviously wrong (incorrect average colour), but this highlights that it is difficult to test a new implementation when even the existing reference implementations return differing results that both look correct at a glance. Without running the image through all existing implementations, it is difficult to know which ones are correct.

Decouple encoding from image processing algorithm

First of all, thank you for this wonderful algorithm, it's a game-changer.

I've read the algorithm code and noticed that currently the encoding is coupled inside the algorithm. I propose to decouple it, storing the raw encode/decode outputs on a byte array and then passing it to the encoding function of our choice.

I can even chime in and do a pull request if this idea gets approbation from you guys.

UMD version

Awesome library! Is it possible to create a UMD compatible version of the final script? I want to use this library in a cordova app that doesn't have a transpile step, so ESM doesn't work for me.

Browser standard?

The moment I learned about this I thought "if only it didn't require the JavaScript but to show"
And the answer to that is simple - propose it to the standards body and get browsers to understand it as a sec attribute on img.
The polyfill practically exists already. Rust and c implementations are ripe for merging in.

I'm not involved with standards tho, it's just a random idea.

Display it's own source code

#include <stdio.h>
int main() {
FILE *fp;
int c;

// open the current input file
fp = fopen(__FILE__,"r");

do {
     c = getc(fp);   // read character 
     putchar(c);     // display character
}
while(c != EOF);  // loop until the end of file is reached

fclose(fp);
return 0;

}

Swift encoder assumes pixel format, has inefficient implementation.

This code assumes that the input image is in RGBA8888 pixel format, specifically that the R,G,B component values are in at offsets 0, 1, 2:

                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])

This isn't always true. This yields blurHashes whose hue is wrong.

The blurHash(numberOfComponents:) method verifies the number of components. At the very least, it should also verify that the alphaInfo is .premultipliedLast. However, that will reject many images. Better would be to remove this assumption from the encoder.

Additionally, the Swift encoder invokes the linearTosRGB() method for every pixel NxM times for blurHashes with components (N, M). If the image is WxH (width, height), the function will be called NxMxWxH. It would dramatically improve perf to break this into two steps: convert all pixels once, then compute factors.

Additionally, given the low fidelity of blurHashes, it probably doesn't make sense to sample every pixel. Another significant perf improvement would be to use a "stride" to sample every Nth pixel vertically and horizontally.

Invalid source maps

Reproduce with:

npm install blurhash
cat node_modules/blurhash/dist/index.js.map

The source map points at a file that does not exist:

"sources":["../src/index.ts"]

As a result, this library cannot be used with Vite which tries to load source maps when the server is started so that it can transform them when it transforms code in the library:

08:57:06 [vite] Internal server error: ENOENT: no such file or directory, open 'node_modules/blurhash/src/index.ts'

Package as ES6 Module (ESM)

The modern way to package JavaScript libraries is as ES6 modules. Unbundled development tools like Snowpack and Vite only work with ESM. While they can accept the CommonJS format that Blurhash provides today, they must first convert the package to ESM which makes it take longer for your development server to startup

Width and height must match the pixels array

Error Width and height must match the pixels array when encoding blurhash with typescript(nodejs)

Implementation

const [buffer, metadata] = await fetchBuffer()

const clampedBuffer = new Uint8ClampedArray(buffer)
const encoded = encodeBlurhash(
   clampedBuffer,
   metadata.width,
   metadata.height,
   4,
   4
)

Inside metadata

{ format: 'jpeg',size: 31912,width: 400,height: 500,space: 'srgb',channels: 3,depth: 'uchar',density: 72,chromaSubsampling: '4:2:0',isProgressive: true,hasProfile: true,hasAlpha: false,icc: <Buffer 00 00 0c 48 4c 69 6e 6f 02 10 00 00 6d 6e 74 72 52 47 42 20 58 59 5a 20 07 ce 00 02 00 09 00 06 00 31 00 00 61 63 73 70 4d 53 46 54 00 00 00 00 49 45 ... 3094 more bytes> } }

fetchBuffer()

function fetchBuffer(): Promise<[Buffer, Record<string, any>]> {
  const options: http.RequestOptions = {
    host: 'images.unsplash.com', 
    path: '/photo-1612383228565-d7423a28bdd7?auto=format&fit=crop&w=400'
  }

  const promises = new Promise<[Buffer, Record<string, any>]>(
    resolve => {
      const chunks = []
      const callback = (response: http.IncomingMessage): void => {
        response.on('data', data => chunks.push(data))
        response.on('end', () => {
          const buffer = Buffer.concat(chunks)

          sharp(buffer).metadata().then(
            info => resolve([buffer, info])
          )
        })
      }

      http.request(options, callback).end()
    }
  )

  return promises
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.