-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit (Decode support only)
- Loading branch information
0 parents
commit 244e235
Showing
14 changed files
with
437 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
language: go | ||
|
||
go: | ||
- 1.10.x | ||
- 1.11.x | ||
- 1.12.x | ||
- tip | ||
|
||
install: | ||
- go get -t -v ./... | ||
- go get -v golang.org/x/tools/cmd/cover | ||
|
||
script: | ||
- go test -v -coverprofile=coverage.txt -covermode=count ./... | ||
|
||
after_success: | ||
- bash <(curl -s https://codecov.io/bash) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2019 Ben Brooks | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# 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. | ||
|
||
Blurhash is written by [Dag Ågren](https://github.com/DagAgren). | ||
|
||
## Contributing | ||
|
||
Issues, feature requests or improvements welcome! | ||
|
||
## Licence | ||
|
||
This project is licensed under the [MIT License](LICENSE). | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package base83 | ||
|
||
import ( | ||
"strings" | ||
) | ||
|
||
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~" | ||
|
||
// Decode decodes a base83 string into an integer value. | ||
func Decode(str string) (val int, err error) { | ||
for i, r := range str { | ||
idx := strings.IndexRune(chars, r) | ||
if idx == -1 { | ||
return 0, invalidError(r, i) | ||
} | ||
|
||
val = val*len(chars) + idx | ||
} | ||
return val, nil | ||
} | ||
|
||
// Encode encodes an integer value into a base83 string of the given length. | ||
func Encode(val, length int) (str string, err error) { | ||
|
||
divisor := 1 | ||
for i := 0; i < length-1; i++ { | ||
divisor *= len(chars) | ||
} | ||
|
||
for i := 0; i < length; i++ { | ||
idx := val / divisor % len(chars) | ||
divisor /= len(chars) | ||
str += string(chars[idx]) | ||
} | ||
|
||
return str, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package base83_test | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
|
||
"github.com/matryer/is" | ||
|
||
"github.com/bbrks/go-blurhash/base83" | ||
) | ||
|
||
var tests = []struct { | ||
str string | ||
val int | ||
}{ | ||
{"3", 3}, | ||
{"A", 10}, | ||
{":", 70}, | ||
{"~", 82}, | ||
{"01", 1}, // leading zeros are "trimmed" | ||
{"11", 84}, | ||
{"33", 252}, | ||
{"~$", 6869}, | ||
{"%%%%%%", 255172974336}, | ||
} | ||
|
||
func TestDecodeEncode(t *testing.T) { | ||
for _, test := range tests { | ||
t.Run(test.str, func(t *testing.T) { | ||
is := is.NewRelaxed(t) | ||
|
||
val, err := base83.Decode(test.str) | ||
is.NoErr(err) // Decode returned unexpected error | ||
is.Equal(val, test.val) // Decode got unexpected result | ||
}) | ||
} | ||
} | ||
|
||
func TestEncode(t *testing.T) { | ||
for _, test := range tests { | ||
t.Run(test.str, func(t *testing.T) { | ||
is := is.NewRelaxed(t) | ||
|
||
str, err := base83.Encode(test.val, len(test.str)) | ||
is.NoErr(err) // Encode returned unexpected error | ||
is.Equal(str, test.str) // Encode got unexpected result | ||
}) | ||
} | ||
} | ||
|
||
func TestDecodeInvalidInput(t *testing.T) { | ||
tests := []struct { | ||
str string | ||
val int | ||
err error | ||
}{ | ||
{"&", 0, base83.ErrInvalidInput}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.str, func(t *testing.T) { | ||
is := is.NewRelaxed(t) | ||
|
||
val, err := base83.Decode(test.str) | ||
is.True(err != nil) // Decode should've returned error for invalid input | ||
is.True(strings.Contains(err.Error(), test.err.Error())) // Decode returned wrong error | ||
is.Equal(val, test.val) // Decode got unexpected result | ||
}) | ||
} | ||
} | ||
|
||
func TestEncodeInvalidLength(t *testing.T) { | ||
tests := []struct { | ||
val int | ||
length int | ||
str string | ||
}{ | ||
{255172974336, 3, "%%%"}, | ||
{255172974336, 6, "%%%%%%"}, | ||
{255172974336, 9, "000%%%%%%"}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.str, func(t *testing.T) { | ||
is := is.NewRelaxed(t) | ||
|
||
output, err := base83.Encode(test.val, test.length) | ||
is.NoErr(err) // Encode should've returned error for invalid input | ||
is.Equal(output, test.str) // Encode got unexpected result | ||
}) | ||
} | ||
} | ||
|
||
func BenchmarkDecode(b *testing.B) { | ||
for _, test := range tests { | ||
b.Run(test.str, func(b *testing.B) { | ||
b.ReportAllocs() | ||
for i := 0; i < b.N; i++ { | ||
_, _ = base83.Decode("~$") | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func BenchmarkEncode(b *testing.B) { | ||
for _, test := range tests { | ||
b.Run(test.str, func(b *testing.B) { | ||
for i := 0; i < b.N; i++ { | ||
_, _ = base83.Encode(6869, 2) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// +build !go1.13 | ||
|
||
package base83 | ||
|
||
import "errors" | ||
|
||
var ErrInvalidInput = errors.New("base83: invalid input") | ||
var ErrInvalidLength = errors.New("base83: invalid length") | ||
|
||
// invalidError returns ErrInvalidInput for the given rune and index | ||
func invalidError(r rune, i int) error { | ||
// No stdlib support for wrapped errors, so return as-is pre-1.13 | ||
return ErrInvalidInput | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// +build go1.13 | ||
|
||
package base83 | ||
|
||
import "fmt" | ||
|
||
func invalidError(c rune, i int) error { | ||
return fmt.Errorf("illegal rune %v at index %d: %w", c, i, ErrInvalidInput) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package blurhash | ||
|
||
import ( | ||
"image" | ||
"image/color" | ||
"math" | ||
|
||
"github.com/bbrks/go-blurhash/base83" | ||
) | ||
|
||
// Components returns the X and Y components of a blurhash. | ||
func Components(hash string) (x, y int, err error) { | ||
sizeFlag, err := base83.Decode(string(hash[0])) | ||
if err != nil { | ||
return 0, 0, err | ||
} | ||
|
||
x = (sizeFlag % 9) + 1 | ||
y = (sizeFlag / 9) + 1 | ||
|
||
expectedLength := 4 + 2*x*y | ||
actualLength := len(hash) | ||
if expectedLength != actualLength { | ||
return 0, 0, lengthError(expectedLength, actualLength) | ||
} | ||
|
||
return x, y, nil | ||
} | ||
|
||
func Decode(hash string, width, height int, punch int) (img image.Image, err error) { | ||
numX, numY, err := Components(hash) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
fPunch := float64(punch) | ||
|
||
quantisedMaximumValue, err := base83.Decode(string(hash[1])) | ||
if err != nil { | ||
return nil, err | ||
} | ||
maximumValue := float64(quantisedMaximumValue+1) / 166 | ||
|
||
// for each component | ||
colors := make([][3]float64, numX*numY) | ||
for i := range colors { | ||
if i == 0 { | ||
val, err := base83.Decode(hash[2:6]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
colors[i] = decodeDC(val) | ||
} else { | ||
val, err := base83.Decode(hash[4+i*2 : 6+i*2]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
colors[i] = decodeAC(float64(val), maximumValue*fPunch) | ||
} | ||
} | ||
|
||
newImg := image.NewNRGBA(image.Rect(0, 0, width, height)) | ||
|
||
for y := 0; y < height; y++ { | ||
for x := 0; x < width; x++ { | ||
var r, g, b float64 | ||
|
||
for j := 0; j < numY; j++ { | ||
for i := 0; i < numX; i++ { | ||
basis := math.Cos(math.Pi*float64(x)*float64(i)/float64(width)) * math.Cos(math.Pi*float64(y)*float64(j)/float64(height)) | ||
compColor := colors[i+j*numX] | ||
r += compColor[0] * basis | ||
g += compColor[1] * basis | ||
b += compColor[2] * basis | ||
} | ||
} | ||
|
||
newImg.SetNRGBA(x, y, color.NRGBA{ | ||
R: uint8(linearTosRGB(r)), | ||
G: uint8(linearTosRGB(g)), | ||
B: uint8(linearTosRGB(b)), | ||
A: 255, | ||
}) | ||
} | ||
} | ||
|
||
return newImg, nil | ||
} | ||
|
||
func decodeDC(val int) (c [3]float64) { | ||
c[0] = sRGBToLinear(val >> 16) | ||
c[1] = sRGBToLinear(val >> 8 & 255) | ||
c[2] = sRGBToLinear(val & 255) | ||
return c | ||
} | ||
|
||
func decodeAC(val, maximumValue float64) (c [3]float64) { | ||
quantR := math.Floor(val / (19 * 19)) | ||
quantG := math.Mod(math.Floor(val/19), 19) | ||
quantB := math.Mod(val, 19) | ||
c[0] = signPow((quantR-9)/9, 2.0) * maximumValue | ||
c[1] = signPow((quantG-9)/9, 2.0) * maximumValue | ||
c[2] = signPow((quantB-9)/9, 2.0) * maximumValue | ||
return c | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package blurhash_test | ||
|
||
import ( | ||
"image/png" | ||
"io/ioutil" | ||
"testing" | ||
|
||
"github.com/matryer/is" | ||
|
||
"github.com/bbrks/go-blurhash" | ||
) | ||
|
||
func TestDecode(t *testing.T) { | ||
for _, test := range testFixtures { | ||
// skip tests without hashes | ||
if test.hash == "" { | ||
continue | ||
} | ||
|
||
t.Run(test.hash, func(t *testing.T) { | ||
is := is.New(t) | ||
|
||
img, err := blurhash.Decode(test.hash, 32, 32, 1) | ||
is.NoErr(err) | ||
|
||
err = png.Encode(ioutil.Discard, img) | ||
is.NoErr(err) | ||
}) | ||
} | ||
} | ||
|
||
func TestComponents(t *testing.T) { | ||
for _, test := range testFixtures { | ||
// skip tests without expected component values | ||
if test.xComp == 0 || test.yComp == 0 { | ||
continue | ||
} | ||
|
||
t.Run(test.hash, func(t *testing.T) { | ||
is := is.NewRelaxed(t) | ||
|
||
x, y, err := blurhash.Components(test.hash) | ||
is.NoErr(err) // unexpected error getting components | ||
is.Equal(x, test.xComp) // blurhash component mismatch | ||
is.Equal(y, test.yComp) // blurhash component mismatch | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// +build !go1.13 | ||
|
||
package blurhash | ||
|
||
import "errors" | ||
|
||
// 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 { | ||
return ErrInvalidHash | ||
} |
Oops, something went wrong.