Forked from
whats-this / cdn-origin
20 commits behind the upstream repository.
transform.go 3.17 KiB
package thumbnailer
import (
"bytes"
"io"
"io/ioutil"
"sync"
"github.com/discordapp/lilliput"
"github.com/pkg/errors"
)
// Transform settings. These should remain constant. If these are ever changed,
// the thumbnail cache should be cleared.
var (
maxInputResolution = 10000 // maximum length in pixels of any input length
maxOutputResolution = 200 // maximum length in pixels of any output length
outputBufferSize = 5 * 1024 * 1024 // bytes
outputType = ".jpeg" // with leading dot
encodeOptions = map[int]int{
lilliput.JpegQuality: 85,
}
)
// Accepted MIME types for thumbnails in map for easy checking
var thumbnailMIMETypes = map[string]struct{}{
"image/gif": struct{}{},
"image/jpeg": struct{}{},
"image/png": struct{}{},
"image/webp": struct{}{},
}
// AcceptedMIMEType checks if a MIME type is suitable for thumbnailing.
func AcceptedMIMEType(mime string) bool {
_, ok := thumbnailMIMETypes[mime]
return ok
}
// Pool operations for reusing *lillput.ImageOps objects.
var imageOpsPool = &sync.Pool{
New: func() interface{} {
return lilliput.NewImageOps(maxInputResolution)
},
}
func getImageOps() *lilliput.ImageOps {
return imageOpsPool.Get().(*lilliput.ImageOps)
}
func returnImageOps(imageOps *lilliput.ImageOps) {
imageOpsPool.Put(imageOps)
}
// Transform takes image data and resizes it to fit the maxOutputResolution above.
func Transform(data io.Reader) (*bytes.Buffer, error) {
b, err := ioutil.ReadAll(data)
if err != nil {
return nil, errors.Wrap(err, "failed to read in image data")
}
// Create decoder and parse header
decoder, err := lilliput.NewDecoder(b)
if err != nil {
return nil, errors.Wrap(err, "failed to decode image data")
}
header, err := decoder.Header()
if err != nil {
return nil, errors.Wrap(err, "failed to parse image header")
}
// Check if the image is within the input limit
if header.Width() > maxInputResolution || header.Height() > maxInputResolution {
return nil, InputTooLarge
}
// Determine output resolution
width := -1
height := -1
if header.Width() == header.Height() {
width = maxOutputResolution
height = maxOutputResolution
} else if header.Width() > header.Height() {
width = 200
scale := header.Width() / maxOutputResolution
height = header.Height() / scale
} else {
height = 200
scale := header.Height() / maxOutputResolution
width = header.Width() / scale
}
// Prepare to resize image
ops := getImageOps()
outputImage := make([]byte, outputBufferSize)
// Resizing options
opts := &lilliput.ImageOptions{
FileType: outputType,
Width: width,
Height: height,
ResizeMethod: lilliput.ImageOpsFit,
NormalizeOrientation: true,
EncodeOptions: encodeOptions,
}
if header.Width() <= maxOutputResolution && header.Height() <= maxOutputResolution {
opts.Width = header.Width()
opts.Height = header.Height()
opts.ResizeMethod = lilliput.ImageOpsNoResize
}
// Transform the image
outputImage, err = ops.Transform(decoder, opts, outputImage)
if err != nil {
return nil, errors.Wrap(err, "failed to transcode image")
}
returnImageOps(ops)
return bytes.NewBuffer(outputImage), err
}