encode.go (4799B)
1 package blurhash 2 3 import ( 4 "fmt" 5 "github.com/buckket/go-blurhash/base83" 6 "image" 7 "math" 8 "strings" 9 ) 10 11 func init() { 12 initLinearTable(channelToLinear[:]) 13 } 14 15 var channelToLinear [256]float64 16 17 func initLinearTable(table []float64) { 18 for i := range table { 19 channelToLinear[i] = sRGBToLinear(i) 20 } 21 } 22 23 // An InvalidParameterError occurs when an invalid argument is passed to either the Decode or Encode function. 24 type InvalidParameterError struct { 25 Value int 26 Parameter string 27 } 28 29 func (e InvalidParameterError) Error() string { 30 return fmt.Sprintf("blurhash: %sComponents (%d) must be element of [1-9]", e.Parameter, e.Value) 31 } 32 33 // An EncodingError represents an error that occurred during the encoding of the given value. 34 // This most likely means that your input image is invalid and can not be processed. 35 type EncodingError string 36 37 func (e EncodingError) Error() string { 38 return fmt.Sprintf("blurhash: %s", string(e)) 39 } 40 41 // Encode calculates the Blurhash for an image using the given x and y component counts. 42 // The x and y components have to be between 1 and 9 respectively. 43 // The image must be of image.Image type. 44 func Encode(xComponents int, yComponents int, rgba image.Image) (string, error) { 45 if xComponents < 1 || xComponents > 9 { 46 return "", InvalidParameterError{xComponents, "x"} 47 } 48 if yComponents < 1 || yComponents > 9 { 49 return "", InvalidParameterError{yComponents, "y"} 50 } 51 52 var blurhash strings.Builder 53 blurhash.Grow(4 + 2*xComponents*yComponents) 54 55 // Size Flag 56 str, err := base83.Encode((xComponents-1)+(yComponents-1)*9, 1) 57 if err != nil { 58 return "", EncodingError("could not encode size flag") 59 } 60 blurhash.WriteString(str) 61 62 factors := make([]float64, yComponents*xComponents*3) 63 multiplyBasisFunction(rgba, factors, xComponents, yComponents) 64 65 var maximumValue float64 66 var quantisedMaximumValue int 67 var acCount = xComponents*yComponents - 1 68 if acCount > 0 { 69 var actualMaximumValue float64 70 for i := 0; i < acCount*3; i++ { 71 actualMaximumValue = math.Max(math.Abs(factors[i+3]), actualMaximumValue) 72 } 73 quantisedMaximumValue = int(math.Max(0, math.Min(82, math.Floor(actualMaximumValue*166-0.5)))) 74 maximumValue = (float64(quantisedMaximumValue) + 1) / 166 75 } else { 76 maximumValue = 1 77 } 78 79 // Quantised max AC component 80 str, err = base83.Encode(quantisedMaximumValue, 1) 81 if err != nil { 82 return "", EncodingError("could not encode quantised max AC component") 83 } 84 blurhash.WriteString(str) 85 86 // DC value 87 str, err = base83.Encode(encodeDC(factors[0], factors[1], factors[2]), 4) 88 if err != nil { 89 return "", EncodingError("could not encode DC value") 90 } 91 blurhash.WriteString(str) 92 93 // AC values 94 for i := 0; i < acCount; i++ { 95 str, err = base83.Encode(encodeAC(factors[3+(i*3+0)], factors[3+(i*3+1)], factors[3+(i*3+2)], maximumValue), 2) 96 if err != nil { 97 return "", EncodingError("could not encode AC value") 98 } 99 blurhash.WriteString(str) 100 } 101 102 if blurhash.Len() != 4+2*xComponents*yComponents { 103 return "", EncodingError("hash does not match expected size") 104 } 105 106 return blurhash.String(), nil 107 } 108 109 func multiplyBasisFunction(rgba image.Image, factors []float64, xComponents int, yComponents int) { 110 height := rgba.Bounds().Max.Y 111 width := rgba.Bounds().Max.X 112 113 xvalues := make([][]float64, xComponents) 114 for xComponent := 0; xComponent < xComponents; xComponent++ { 115 xvalues[xComponent] = make([]float64, width) 116 for x := 0; x < width; x++ { 117 xvalues[xComponent][x] = math.Cos(math.Pi * float64(xComponent) * float64(x) / float64(width)) 118 } 119 } 120 121 yvalues := make([][]float64, yComponents) 122 for yComponent := 0; yComponent < yComponents; yComponent++ { 123 yvalues[yComponent] = make([]float64, height) 124 for y := 0; y < height; y++ { 125 yvalues[yComponent][y] = math.Cos(math.Pi * float64(yComponent) * float64(y) / float64(height)) 126 } 127 } 128 129 for y := 0; y < height; y++ { 130 for x := 0; x < width; x++ { 131 rt, gt, bt, _ := rgba.At(x, y).RGBA() 132 lr := channelToLinear[rt>>8] 133 lg := channelToLinear[gt>>8] 134 lb := channelToLinear[bt>>8] 135 136 for yc := 0; yc < yComponents; yc++ { 137 for xc := 0; xc < xComponents; xc++ { 138 139 scale := 1 / float64(width*height) 140 141 if xc != 0 || yc != 0 { 142 scale = 2 / float64(width*height) 143 } 144 145 basis := xvalues[xc][x] * yvalues[yc][y] 146 factors[0+xc*3+yc*3*xComponents] += lr * basis * scale 147 factors[1+xc*3+yc*3*xComponents] += lg * basis * scale 148 factors[2+xc*3+yc*3*xComponents] += lb * basis * scale 149 } 150 } 151 } 152 } 153 } 154 155 func encodeDC(r, g, b float64) int { 156 return (linearTosRGB(r) << 16) + (linearTosRGB(g) << 8) + linearTosRGB(b) 157 } 158 159 func encodeAC(r, g, b, maximumValue float64) int { 160 quant := func(f float64) int { 161 return int(math.Max(0, math.Min(18, math.Floor(signPow(f/maximumValue, 0.5)*9+9.5)))) 162 } 163 return quant(r)*19*19 + quant(g)*19 + quant(b) 164 }