set custom output/aspect/scale formats, add package-level error vars
This commit is contained in:
parent
95da07dbb9
commit
67c50a0539
@ -1,7 +1,7 @@
|
||||
package thumbnail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
@ -12,22 +12,55 @@ import (
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
type OutputFormat uint8
|
||||
type AspectFormat uint8
|
||||
type ScaleFormat uint8
|
||||
|
||||
const (
|
||||
OUTPUT_PNG_CRUSH OutputFormat = 2
|
||||
OUTPUT_JPG OutputFormat = 3
|
||||
OUTPUT__DEFAULT = OUTPUT_PNG_CRUSH
|
||||
|
||||
ASPECT_PADDED AspectFormat = 80
|
||||
ASPECT_SVELTE AspectFormat = 81
|
||||
ASPECT__DEFAULT = ASPECT_PADDED
|
||||
|
||||
SCALEFMT_NN ScaleFormat = 120
|
||||
SCALEFMT_BILINEAR ScaleFormat = 121
|
||||
SCALEFMT__DEFAULT = SCALEFMT_BILINEAR
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidOption error = errors.New("Invalid format parameter")
|
||||
ErrUnsupportedFiletype error = errors.New("Unsupported filetype")
|
||||
)
|
||||
|
||||
type Thumbnailer struct {
|
||||
Width int
|
||||
Height int
|
||||
width int
|
||||
height int
|
||||
ofmt OutputFormat
|
||||
afmt AspectFormat
|
||||
sfmt ScaleFormat
|
||||
|
||||
thumbCache *lru.Cache // threadsafe
|
||||
}
|
||||
|
||||
func NewThumbnailer(Width, Height, MaxCacheSize int) *Thumbnailer {
|
||||
return NewThumbnailerEx(Width, Height, MaxCacheSize, OUTPUT__DEFAULT, ASPECT__DEFAULT, SCALEFMT__DEFAULT)
|
||||
}
|
||||
|
||||
func NewThumbnailerEx(Width, Height, MaxCacheSize int, of OutputFormat, af AspectFormat, sf ScaleFormat) *Thumbnailer {
|
||||
thumbCache, err := lru.New(MaxCacheSize)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &Thumbnailer{
|
||||
Width: Width,
|
||||
Height: Height,
|
||||
width: Width,
|
||||
height: Height,
|
||||
ofmt: of,
|
||||
afmt: af,
|
||||
sfmt: sf,
|
||||
thumbCache: thumbCache,
|
||||
}
|
||||
}
|
||||
@ -98,7 +131,7 @@ func (this *Thumbnailer) RenderFile_NoCache(absPath string) ([]byte, error) {
|
||||
return this.RenderScaledFfmpeg(absPath)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("No thumbnailer for file type '%s'", extension)
|
||||
return nil, ErrUnsupportedFiletype
|
||||
|
||||
}
|
||||
|
||||
|
60
image.go
60
image.go
@ -1,7 +1,9 @@
|
||||
package thumbnail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
|
||||
"code.ivysaur.me/imagequant"
|
||||
)
|
||||
@ -13,32 +15,27 @@ func (this *Thumbnailer) RenderScaledImage(src image.Image) ([]byte, error) {
|
||||
destH := 0
|
||||
|
||||
if srcW > srcH {
|
||||
destW = this.Width
|
||||
destH = this.Height * srcH / srcW
|
||||
destW = this.width
|
||||
destH = this.height * srcH / srcW
|
||||
} else {
|
||||
destW = this.Width * srcW / srcH
|
||||
destH = this.Height
|
||||
destW = this.width * srcW / srcH
|
||||
destH = this.height
|
||||
}
|
||||
|
||||
offsetX := (this.Width - destW) / 2
|
||||
offsetY := (this.Height - destH) / 2
|
||||
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}})
|
||||
dest := image.NewRGBA(image.Rectangle{Max: image.Point{X: this.width, Y: this.height}})
|
||||
|
||||
switch this.sfmt {
|
||||
|
||||
case SCALEFMT_BILINEAR:
|
||||
|
||||
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))
|
||||
@ -49,5 +46,34 @@ func (this *Thumbnailer) RenderScaledImage(src image.Image) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return crush(dest, imagequant.SPEED_FASTEST)
|
||||
case SCALEFMT_NN:
|
||||
|
||||
for y := 0; y < destH; y += 1 {
|
||||
for x := 0; x < destW; x += 1 {
|
||||
mapx := int(float64(x) * scaleW)
|
||||
mapy := int(float64(y) * scaleH)
|
||||
dest.Set(x+offsetX, y+offsetY, src.At(mapx, mapy))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch this.ofmt {
|
||||
case OUTPUT_PNG_CRUSH:
|
||||
return crush(dest, imagequant.SPEED_FASTEST)
|
||||
|
||||
case OUTPUT_JPG:
|
||||
buff := bytes.Buffer{}
|
||||
err := jpeg.Encode(&buff, dest, &jpeg.Options{Quality: jpeg.DefaultQuality})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buff.Bytes(), nil
|
||||
|
||||
default:
|
||||
return nil, ErrInvalidOption
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
18
video.go
18
video.go
@ -11,10 +11,22 @@ func (this *Thumbnailer) RenderScaledFfmpeg(absPath string) ([]byte, error) {
|
||||
|
||||
scaleCmd := fmt.Sprintf(
|
||||
`thumbnail,scale='if(gt(a,%d/%d),%d,-1)':'if(gt(a,%d/%d),-1,%d)'`,
|
||||
this.Height, this.Width, this.Height,
|
||||
this.Height, this.Width, this.Width,
|
||||
this.height, this.width, this.height,
|
||||
this.height, this.width, this.width,
|
||||
)
|
||||
|
||||
// BUG(): always produces ASPECT_SVELTE shape - need to re-pad for ASPECT_PADDED
|
||||
|
||||
var vcodec string
|
||||
switch this.ofmt {
|
||||
case OUTPUT_JPG:
|
||||
vcodec = "jpg"
|
||||
case OUTPUT_PNG_CRUSH:
|
||||
vcodec = "png"
|
||||
default:
|
||||
return nil, ErrInvalidOption
|
||||
}
|
||||
|
||||
cmd := exec.Command(
|
||||
"ffmpeg",
|
||||
"-loglevel", "0",
|
||||
@ -23,7 +35,7 @@ func (this *Thumbnailer) RenderScaledFfmpeg(absPath string) ([]byte, error) {
|
||||
"-vf", scaleCmd,
|
||||
"-frames:v", "1",
|
||||
"-f", "image2pipe",
|
||||
"-c:v", "png",
|
||||
"-c:v", vcodec,
|
||||
"-",
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user