decode.go (2914B)
1 package blurhash 2 3 import ( 4 "fmt" 5 "github.com/buckket/go-blurhash/base83" 6 "image" 7 "image/color" 8 "math" 9 ) 10 11 // An InvalidHashError occurs when the given hash is either too short or the length does not match its size flag. 12 type InvalidHashError string 13 14 func (e InvalidHashError) Error() string { 15 return fmt.Sprintf("blurhash: %s", string(e)) 16 } 17 18 // Components decodes and returns the number of x and y components in the given BlurHash. 19 func Components(hash string) (xComponents, yComponents int, err error) { 20 if len(hash) < 6 { 21 return 0, 0, InvalidHashError("hash is invalid (too short)") 22 } 23 24 sizeFlag, err := base83.Decode(string(hash[0])) 25 if err != nil { 26 return 0, 0, err 27 } 28 29 yComponents = (sizeFlag / 9) + 1 30 xComponents = (sizeFlag % 9) + 1 31 32 if len(hash) != 4+2*xComponents*yComponents { 33 return 0, 0, InvalidHashError("hash is invalid (length mismatch)") 34 } 35 36 return xComponents, yComponents, nil 37 } 38 39 // Decode generates an image of the given BlurHash with a size of width and height. 40 // Punch is a multiplier that adjusts the contrast of the resulting image. 41 func Decode(hash string, width, height, punch int) (image.Image, error) { 42 xComp, yComp, err := Components(hash) 43 if err != nil { 44 return nil, err 45 } 46 47 quantisedMaximumValue, err := base83.Decode(string(hash[1])) 48 if err != nil { 49 return nil, err 50 } 51 maximumValue := (float64(quantisedMaximumValue) + 1) / 166 52 53 if punch == 0 { 54 punch = 1 55 } 56 57 colors := make([][3]float64, xComp*yComp) 58 59 for i := range colors { 60 if i == 0 { 61 value, err := base83.Decode(hash[2:6]) 62 if err != nil { 63 return nil, err 64 } 65 colors[i] = decodeDC(value) 66 } else { 67 value, err := base83.Decode(hash[4+i*2 : 6+i*2]) 68 if err != nil { 69 return nil, err 70 } 71 colors[i] = decodeAC(value, maximumValue*float64(punch)) 72 } 73 } 74 75 img := image.NewNRGBA(image.Rect(0, 0, width, height)) 76 77 for y := 0; y < height; y++ { 78 for x := 0; x < width; x++ { 79 var r, g, b float64 80 for j := 0; j < yComp; j++ { 81 for i := 0; i < xComp; i++ { 82 basis := math.Cos(math.Pi*float64(x)*float64(i)/float64(width)) * 83 math.Cos(math.Pi*float64(y)*float64(j)/float64(height)) 84 pcolor := colors[i+j*xComp] 85 r += pcolor[0] * basis 86 g += pcolor[1] * basis 87 b += pcolor[2] * basis 88 } 89 } 90 img.SetNRGBA(x, y, color.NRGBA{R: uint8(linearTosRGB(r)), G: uint8(linearTosRGB(g)), B: uint8(linearTosRGB(b)), A: 255}) 91 } 92 } 93 94 return img, nil 95 } 96 97 func decodeDC(value int) [3]float64 { 98 return [3]float64{sRGBToLinear(value >> 16), sRGBToLinear(value >> 8 & 255), sRGBToLinear(value & 255)} 99 } 100 101 func decodeAC(value int, maximumValue float64) [3]float64 { 102 quantR := math.Floor(float64(value) / (19 * 19)) 103 quantG := math.Mod(math.Floor(float64(value)/19), 19) 104 quantB := math.Mod(float64(value), 19) 105 sp := func(quant float64) float64 { 106 return signPow((quant-9)/9, 2.0) * maximumValue 107 } 108 return [3]float64{sp(quantR), sp(quantG), sp(quantB)} 109 }