gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }