mtsatellite/common/image.go

163 lines
3.2 KiB
Go

// 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)
}