From 51917511de7570f18f752844356c70bc4c168eaa Mon Sep 17 00:00:00 2001 From: Ben Brooks Date: Tue, 14 May 2019 00:07:54 +0100 Subject: [PATCH] Add Encoding support to match C version --- README.md | 7 +-- decode_test.go | 32 +++++++++- encode.go | 144 +++++++++++++++++++++++++++++++++++++++++++ encode_test.go | 67 ++++++++++++++++++++ error.go | 4 ++ fixtures/octocat.png | Bin 0 -> 28804 bytes fixtures_test.go | 1 + util.go | 4 +- 8 files changed, 252 insertions(+), 7 deletions(-) create mode 100644 encode.go create mode 100644 encode_test.go create mode 100644 fixtures/octocat.png diff --git a/README.md b/README.md index 7c6b904..0180f52 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # go-blurhash [![Build Status](https://travis-ci.org/bbrks/go-blurhash.svg)](https://travis-ci.org/bbrks/go-blurhash) [![codecov](https://codecov.io/gh/bbrks/go-blurhash/branch/master/graph/badge.svg)](https://codecov.io/gh/bbrks/go-blurhash) [![GoDoc](https://godoc.org/github.com/bbrks/go-blurhash?status.svg)](https://godoc.org/github.com/bbrks/go-blurhash) [![Go Report Card](https://goreportcard.com/badge/github.com/bbrks/go-blurhash)](https://goreportcard.com/report/github.com/bbrks/go-blurhash) [![GitHub tag](https://img.shields.io/github/tag/bbrks/go-blurhash.svg)](https://github.com/bbrks/go-blurhash/releases) [![license](https://img.shields.io/github/license/bbrks/go-blurhash.svg)](https://github.com/bbrks/go-blurhash/blob/master/LICENSE) -A pure Go implementation of Blurhash. Right now, almost a straight up port of the [C](https://github.com/Gargron/blurhash) and [TypeScript](https://github.com/Gargron/blurhash.js) versions, slightly adapted to Go. +A pure Go implementation of Blurhash. The API is stable, however the hashing function in either direction may not be. -Blurhash is an algorithm that encodes an image into a short (~20-30 byte) ASCII string. When you decode the string back into an image, you get a gradient of colors that represent the original image. This can be useful for scenarios where you want an image placeholder before loading, or even to censor the contents of an image [a la Mastodon](https://blog.joinmastodon.org/2019/05/improving-support-for-adult-content-on-mastodon/). +Blurhash is an algorithm written by [Dag Ågren](https://github.com/DagAgren) that encodes an image into a short (~20-30 byte) ASCII string. When you decode the string back into an image, you get a gradient of colors that represent the original image. This can be useful for scenarios where you want an image placeholder before loading, or even to censor the contents of an image [a la Mastodon](https://blog.joinmastodon.org/2019/05/improving-support-for-adult-content-on-mastodon/). -Blurhash is written by [Dag Ågren](https://github.com/DagAgren). +Under the covers, this library is almost a straight port of the [C version](https://github.com/Gargron/blurhash). ## Contributing @@ -13,4 +13,3 @@ Issues, feature requests or improvements welcome! ## Licence This project is licensed under the [MIT License](LICENSE). - diff --git a/decode_test.go b/decode_test.go index 484528d..9be2ed8 100644 --- a/decode_test.go +++ b/decode_test.go @@ -32,7 +32,7 @@ func TestDecode(t *testing.T) { func TestComponents(t *testing.T) { for _, test := range testFixtures { // skip tests without expected component values - if test.xComp == 0 || test.yComp == 0 { + if test.hash == "" || test.xComp == 0 || test.yComp == 0 { continue } @@ -46,3 +46,33 @@ func TestComponents(t *testing.T) { }) } } + +func BenchmarkComponents(b *testing.B) { + for _, test := range testFixtures { + // skip tests without hashes + if test.hash == "" { + continue + } + + b.Run(test.hash, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _, _ = blurhash.Components(test.hash) + } + }) + } +} + +func BenchmarkDecode(b *testing.B) { + for _, test := range testFixtures { + // skip tests without hashes + if test.hash == "" { + continue + } + + b.Run(test.hash, func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = blurhash.Decode(test.hash, 32, 32, 1) + } + }) + } +} diff --git a/encode.go b/encode.go new file mode 100644 index 0000000..c8e411e --- /dev/null +++ b/encode.go @@ -0,0 +1,144 @@ +package blurhash + +import ( + "image" + "image/color" + "math" + "strings" + + "github.com/bbrks/go-blurhash/base83" +) + +const ( + minComponents = 1 + maxComponents = 9 +) + +// Encode returns the blurhash for the given image. +func Encode(xComponents, yComponents int, img image.Image) (hash string, err error) { + if xComponents < minComponents || xComponents > maxComponents || + yComponents < minComponents || yComponents > maxComponents { + return "", ErrInvalidComponents + } + + b := strings.Builder{} + + sizeFlag := (xComponents - 1) + (yComponents-1)*9 + sizeFlagEncoded, err := base83.Encode(sizeFlag, 1) + if err != nil { + return "", err + } + + _, err = b.WriteString(sizeFlagEncoded) + if err != nil { + return "", err + } + + // vector of yComponents*xComponents*(RGB) + factors := make([][][3]float64, yComponents, yComponents) + for y := 0; y < yComponents; y++ { + factors[y] = make([][3]float64, xComponents, xComponents) + for x := 0; x < xComponents; x++ { + factor := multiplyBasisFunction(x, y, img) + factors[y][x][0] = factor[0] + factors[y][x][1] = factor[1] + factors[y][x][2] = factor[2] + } + } + + maximumValue := 0.0 + if xComponents*yComponents-1 > 0 { + actualMaximumValue := 0.0 + for y := 0; y < yComponents; y++ { + for x := 0; x < xComponents; x++ { + if y == 0 && x == 0 { + continue + } + actualMaximumValue = math.Max(math.Abs(factors[y][x][0]), actualMaximumValue) + actualMaximumValue = math.Max(math.Abs(factors[y][x][1]), actualMaximumValue) + actualMaximumValue = math.Max(math.Abs(factors[y][x][2]), actualMaximumValue) + } + } + + quantisedMaximumValue := math.Max(0, math.Min(82, math.Floor(actualMaximumValue*166-0.5))) + maximumValue = (quantisedMaximumValue + 1) / 166 + str, err := base83.Encode(int(quantisedMaximumValue), 1) + if err != nil { + return "", err + } + b.WriteString(str) + } else { + maximumValue = 1 + str, err := base83.Encode(0, 1) + if err != nil { + return "", err + } + b.WriteString(str) + } + + dc := factors[0][0] + str, err := base83.Encode(encodeDC(dc[0], dc[1], dc[2]), 4) + if err != nil { + return "", err + } + b.WriteString(str) + + for y := 0; y < yComponents; y++ { + for x := 0; x < xComponents; x++ { + if y == 0 && x == 0 { + continue + } + str, err := base83.Encode(encodeAC(factors[y][x][0], factors[y][x][1], factors[y][x][2], maximumValue), 2) + if err != nil { + return "", err + } + b.WriteString(str) + } + } + + return b.String(), nil +} + +func encodeDC(r, g, b float64) int { + return (linearTosRGB(r) << 16) + (linearTosRGB(g) << 8) + linearTosRGB(b) +} + +func encodeAC(r, g, b, maximumValue float64) int { + quantR := math.Max(0, math.Min(18, math.Floor(signPow(r/maximumValue, 0.5)*9+9.5))) + quantG := math.Max(0, math.Min(18, math.Floor(signPow(g/maximumValue, 0.5)*9+9.5))) + quantB := math.Max(0, math.Min(18, math.Floor(signPow(b/maximumValue, 0.5)*9+9.5))) + + return int(quantR*19*19 + quantG*19 + quantB) +} + +func multiplyBasisFunction(xComponents, yComponents int, img image.Image) [3]float64 { + var r, g, b float64 + width, height := float64(img.Bounds().Dx()), float64(img.Bounds().Dy()) + + normalisation := 2.0 + if xComponents == 0 && yComponents == 0 { + normalisation = 1.0 + } + + for x := 0; x < img.Bounds().Dx(); x++ { + for y := 0; y < img.Bounds().Max.Y; y++ { + //cR, cG, cB, _ := img.At(x, y).RGBA() + c, ok := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA) + if !ok { + panic("not color.NRGBA") + } + basis := math.Cos(math.Pi*float64(xComponents)*float64(x)/width) * + math.Cos(math.Pi*float64(yComponents)*float64(y)/height) + r += basis * sRGBToLinear(int(c.R)) + g += basis * sRGBToLinear(int(c.G)) + b += basis * sRGBToLinear(int(c.B)) + } + } + + scale := normalisation / (width * height) + return [3]float64{ + r * scale, + g * scale, + b * scale, + } +} diff --git a/encode_test.go b/encode_test.go new file mode 100644 index 0000000..a506a0b --- /dev/null +++ b/encode_test.go @@ -0,0 +1,67 @@ +package blurhash_test + +import ( + "image" + "os" + "path/filepath" + "testing" + + "github.com/matryer/is" + + "github.com/bbrks/go-blurhash" +) + +func TestEncode(t *testing.T) { + for _, test := range testFixtures { + if test.file == "" { + // skip tests without files + continue + } + + t.Run(test.hash, func(t *testing.T) { + is := is.New(t) + + f, err := os.Open(filepath.FromSlash(test.file)) + is.NoErr(err) // error opening test fixture file + defer f.Close() + + is.True(f != nil) // file should not be nil + + img, _, err := image.Decode(f) + is.NoErr(err) // error decoding image from test fixture + is.True(img != nil) // image should not be nil + + hash, err := blurhash.Encode(test.xComp, test.yComp, img) + is.NoErr(err) // error hashing test fixture image + is.Equal(hash, test.hash) // blurhash mismatch + }) + } +} + +func BenchmarkEncode(b *testing.B) { + for _, test := range testFixtures { + if test.file == "" { + // skip tests without files + continue + } + + b.Run(test.hash, func(b *testing.B) { + is := is.New(b) + + f, err := os.Open(filepath.FromSlash(test.file)) + is.NoErr(err) // error opening test fixture file + defer f.Close() + + is.True(f != nil) // file should not be nil + + img, _, err := image.Decode(f) + is.NoErr(err) // error decoding image from test fixture + is.True(img != nil) // image should not be nil + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _ = blurhash.Encode(test.xComp, test.yComp, img) + } + }) + } +} diff --git a/error.go b/error.go index 528858c..76be0db 100644 --- a/error.go +++ b/error.go @@ -2,9 +2,13 @@ package blurhash import "errors" +// ErrInvalidComponents is returned when components passed to Encode are invalid. +var ErrInvalidComponents = errors.New("blurhash: must have between 1 and 9 components") + // ErrInvalidHash is returned when the library encounters a hash it can't recognise. var ErrInvalidHash = errors.New("blurhash: invalid hash") func lengthError(expectedLength, actualLength int) error { + // No stdlib support for wrapped errors, so return as-is pre-1.13 return ErrInvalidHash } diff --git a/fixtures/octocat.png b/fixtures/octocat.png new file mode 100644 index 0000000000000000000000000000000000000000..08f171c95c10212cad1ab98d9f41cc78587acf3d GIT binary patch literal 28804 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