initial commit
This commit is contained in:
commit
081f542e08
185
thumbnail.go
Normal file
185
thumbnail.go
Normal file
@ -0,0 +1,185 @@
|
||||
// Package thumbnail contains functions for taking the thumbnail of image and
|
||||
// video files, and caching the result for performance.
|
||||
package thumbnail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
"sync"
|
||||
//"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Thumbnailer struct {
|
||||
Width int
|
||||
Height int
|
||||
MaxCacheSize int
|
||||
|
||||
cacheMtx sync.RWMutex
|
||||
thumbCache map[string][]byte
|
||||
}
|
||||
|
||||
func NewThumbnailer() *Thumbnailer {
|
||||
return &Thumbnailer{
|
||||
Width: 100,
|
||||
Height: 100,
|
||||
MaxCacheSize: 10,
|
||||
thumbCache: make(map[string][]byte, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func videoThumbnail(absPath string) {
|
||||
/*
|
||||
cmd := exec.Command(
|
||||
"ffmpeg",
|
||||
"-loglevel", "0",
|
||||
"-an",
|
||||
"-i", absPath,
|
||||
"-vf", "thumbnail,scale=100:100",
|
||||
"-frames:v", "1",
|
||||
"-f", "image2pipe",
|
||||
"-c:v", "png",
|
||||
"-",
|
||||
)
|
||||
*/
|
||||
}
|
||||
|
||||
func (this *Thumbnailer) RenderFile(absPath string) ([]byte, error) {
|
||||
this.cacheMtx.RLock()
|
||||
tcache, ok := this.thumbCache[absPath]
|
||||
this.cacheMtx.RUnlock()
|
||||
|
||||
if ok {
|
||||
return tcache, nil
|
||||
}
|
||||
|
||||
// Add to cache
|
||||
tcache, err := this.RenderFile_NoCache(absPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
this.cacheMtx.Lock()
|
||||
// FIXME prune old cached thumbnails
|
||||
this.thumbCache[absPath] = tcache
|
||||
this.cacheMtx.Unlock()
|
||||
|
||||
return tcache, nil
|
||||
}
|
||||
|
||||
func (this *Thumbnailer) RenderFile_NoCache(absPath string) ([]byte, error) {
|
||||
|
||||
fh, err := os.OpenFile(absPath, os.O_RDONLY, 0400)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer fh.Close()
|
||||
|
||||
var src image.Image
|
||||
err = fmt.Errorf("No thumbnailer for file type")
|
||||
|
||||
comparePath := strings.ToLower(absPath)
|
||||
|
||||
if strings.HasSuffix(comparePath, "jpg") {
|
||||
src, err = jpeg.Decode(fh)
|
||||
|
||||
} else if strings.HasSuffix(comparePath, "png") {
|
||||
src, err = png.Decode(fh)
|
||||
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return this.RenderScaledImage(src)
|
||||
}
|
||||
|
||||
func Blend(a, b color.Color) color.Color {
|
||||
switch a.(type) {
|
||||
case color.RGBA:
|
||||
return BlendRGBA(a.(color.RGBA), b.(color.RGBA)) // FIXME there's syntax for this
|
||||
|
||||
case color.YCbCr:
|
||||
return BlendYCbCr(a.(color.YCbCr), b.(color.YCbCr))
|
||||
|
||||
default:
|
||||
return a // ??? unknown color format
|
||||
}
|
||||
}
|
||||
|
||||
func BlendYCbCr(a, b color.YCbCr) color.YCbCr {
|
||||
return color.YCbCr{
|
||||
Y: uint8((int(a.Y) + int(b.Y)) / 2),
|
||||
Cb: uint8((int(a.Cb) + int(b.Cb)) / 2),
|
||||
Cr: uint8((int(a.Cr) + int(b.Cr)) / 2),
|
||||
}
|
||||
}
|
||||
|
||||
func BlendRGBA(a, b color.RGBA) color.RGBA {
|
||||
return color.RGBA{
|
||||
R: uint8((int(a.R) + int(b.R)) / 2),
|
||||
G: uint8((int(a.G) + int(b.G)) / 2),
|
||||
B: uint8((int(a.B) + int(b.B)) / 2),
|
||||
A: uint8((int(a.A) + int(b.A)) / 2),
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Thumbnailer) RenderScaledImage(src image.Image) ([]byte, error) {
|
||||
srcW := src.Bounds().Max.X
|
||||
srcH := src.Bounds().Max.Y
|
||||
destW := 0
|
||||
destH := 0
|
||||
|
||||
if srcW > srcH {
|
||||
destW = this.Width
|
||||
destH = this.Height * srcH / srcW
|
||||
} else {
|
||||
destW = this.Width * srcW / srcH
|
||||
destH = this.Height
|
||||
}
|
||||
|
||||
offsetX := (this.Width - destW) / 2
|
||||
offsetY := (this.Height - destH) / 2
|
||||
|
||||
scaleW := float64(srcW) / float64(destW)
|
||||
scaleH := float64(srcH) / float64(destH)
|
||||
|
||||
dest := image.NewRGBA(image.Rectangle{Max: image.Point{X: this.Width, Y: this.Height}})
|
||||
|
||||
for y := 0; y < destH; y += 1 {
|
||||
for x := 0; x < destW; x += 1 {
|
||||
|
||||
/*
|
||||
// NN
|
||||
mapx := int(float64(x) * scaleW)
|
||||
mapy := int(float64(y) * scaleH)
|
||||
dest.Set(x+offsetX, y+offsetY, src.At(mapx, mapy))
|
||||
*/
|
||||
|
||||
// Bilinear
|
||||
c00 := src.At(int(float64(x)*scaleW), int(float64(y)*scaleH))
|
||||
c01 := src.At(int((float64(x)+0.5)*scaleW), int(float64(y)*scaleH))
|
||||
c10 := src.At(int(float64(x)*scaleW), int((float64(y)+0.5)*scaleH))
|
||||
c11 := src.At(int((float64(x)+0.5)*scaleW), int((float64(y)+0.5)*scaleH))
|
||||
cBlend := Blend(Blend(c00, c01), Blend(c10, c11))
|
||||
dest.Set(x+offsetX, y+offsetY, cBlend)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
err := png.Encode(&b, dest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user