// Copyright 2014, 2015 by Sascha L. Teichmann // Use of this source code is governed by the MIT license // that can be found in the LICENSE file. package common import ( "bufio" "bytes" "crypto/sha1" "errors" "image" "image/color" "image/png" "log" "os" "strconv" "sync" "time" "github.com/bamiaux/rez" ) // ResizeFilter is used to scale down the pyramid tiles. var ResizeFilter = rez.NewLanczosFilter(3) var rrand uint32 var rrandmu sync.Mutex func reseed() uint32 { return uint32(time.Now().UnixNano() + int64(os.Getpid())) } func nextSuffix() string { rrandmu.Lock() r := rrand if r == 0 { r = reseed() } r = r*1664525 + 1013904223 // constants from Numerical Recipes rrand = r rrandmu.Unlock() return strconv.Itoa(int(1e9 + r%1e9))[1:] } func EncodeToMem(img image.Image) ([]byte, error) { var buf bytes.Buffer enc := png.Encoder{png.BestCompression} if err := enc.Encode(&buf, img); err != nil { return nil, err } return buf.Bytes(), nil } func SaveAsPNG(path string, img image.Image) (err error) { var file *os.File if file, err = os.Create(path); err != nil { return } writer := bufio.NewWriter(file) err = png.Encode(writer, img) writer.Flush() file.Close() return } func tmpName(tmpl string) (string, error) { tmpPre := tmpl + ".tmp" nconflict := 0 for i := 0; i < 10000; i++ { tmp := tmpPre + nextSuffix() if _, err := os.Stat(tmp); err != nil { if os.IsNotExist(err) { return tmp, nil } return "", err } if nconflict++; nconflict > 10 { nconflict = 0 rrand = reseed() } } return "", errors.New("Cannot create temp name") } func SaveAsPNGAtomic(path string, img image.Image) (err error) { var tmpPath string if tmpPath, err = tmpName(path); err != nil { return } // Still a bit racy if err = SaveAsPNG(tmpPath, img); err != nil { return } return os.Rename(tmpPath, path) } func LoadPNG(path string, bg color.RGBA) image.Image { var err error var file *os.File if file, err = os.Open(path); err != nil { return image.NewUniform(bg) } defer file.Close() reader := bufio.NewReader(file) var img image.Image if img, err = png.Decode(reader); err != nil { log.Printf("WARN: decoding '%s' failed: %s\n", path, err) return image.NewUniform(bg) } return img } func sha1rgba(img *image.RGBA) []byte { hash := sha1.New() w, h := img.Rect.Dx()*4, img.Rect.Dy() pos := img.PixOffset(img.Rect.Min.X, img.Rect.Min.Y) for ; h > 0; h, pos = h-1, pos+img.Stride { hash.Write(img.Pix[pos : pos+w]) } return hash.Sum(nil) } func sha1uniform(img *image.Uniform) []byte { r, g, b, a := img.C.RGBA() return sha1.New().Sum([]byte{ byte(r >> 16), byte(g >> 16), byte(b >> 16), byte(a >> 16)}) } func SHA1Image(img image.Image) []byte { switch i := img.(type) { case *image.RGBA: return sha1rgba(i) case *image.Uniform: return sha1uniform(i) } log.Println("WARN: slow path for SHA1Image") hash := sha1.New() bounds := img.Bounds() var pix [4]byte for x := bounds.Min.X; x <= bounds.Max.X; x++ { for y := bounds.Min.Y; y <= bounds.Max.Y; y++ { r, g, b, a := img.At(x, y).RGBA() pix[0] = byte(r >> 16) pix[1] = byte(g >> 16) pix[2] = byte(b >> 16) pix[3] = byte(a >> 16) hash.Write(pix[:]) } } return hash.Sum(nil) }