123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- package media
- import (
- "bytes"
- "encoding/json"
- "image"
- "image/color"
- "image/draw"
- "image/gif"
- _ "image/jpeg"
- "io/ioutil"
- "math"
- "github.com/disintegration/imaging"
- )
- var mediaHandlers = make(map[string]MediaHandler)
- var DefaultSizeKey = "default"
- // MediaHandler media library handler interface, defined which files could be handled, and the handler
- type MediaHandler interface {
- CouldHandle(media Media) bool
- Handle(media Media, file FileInterface, option *Option) error
- }
- // RegisterMediaHandler register Media library handler
- func RegisterMediaHandler(name string, handler MediaHandler) {
- mediaHandlers[name] = handler
- }
- // imageHandler default image handler
- type imageHandler struct{}
- func (imageHandler) CouldHandle(media Media) bool {
- return media.IsImage()
- }
- func resizeImageTo(img image.Image, size *Size, format imaging.Format) image.Image {
- imgSize := img.Bounds().Size()
- switch {
- case size.Padding:
- var (
- backgroundColor = image.NewUniform(color.Transparent)
- ratioX = float64(size.Width) / float64(imgSize.X)
- ratioY = float64(size.Height) / float64(imgSize.Y)
- // 100x200 -> 200x300 ==> ratioX = 2, ratioY = 1.5 ==> resize to (x1.5) = 150x300
- // 100x200 -> 20x50 ==> ratioX = 0.2, ratioY = 0.4 ==> resize to (x0.2) = 20x40
- // 100x200 -> 50x0 ==> ratioX = 0.5, ratioY = 0 ==> resize to (x0.5) = 50x100
- minRatio = math.Min(ratioX, ratioY)
- )
- if format == imaging.JPEG {
- backgroundColor = image.NewUniform(color.White)
- }
- background := imaging.New(size.Width, size.Height, backgroundColor)
- fixFloat := func(x float64, y int) int {
- if math.Abs(x-float64(y)) < 1 {
- return y
- }
- return int(x)
- }
- if minRatio == 0 {
- minRatio = math.Max(ratioX, ratioY)
- if size.Width == 0 && size.Height != 0 {
- // size 50x0, source 100x200 => crop to 50x100
- newWidth := int(float64(imgSize.X) / float64(imgSize.Y) * float64(size.Height))
- background = imaging.New(newWidth, size.Height, backgroundColor)
- } else if size.Height == 0 && size.Width != 0 {
- // size 0x50, source 100x200 => crop to 25x50
- newHeight := int(float64(imgSize.Y) / float64(imgSize.X) * float64(size.Width))
- background = imaging.New(size.Width, newHeight, backgroundColor)
- } else if size.Height == 0 && size.Width == 0 {
- minRatio = 1
- background = imaging.New(imgSize.X, imgSize.Y, backgroundColor)
- }
- }
- backgroundSize := background.Bounds().Size()
- img = imaging.Resize(img, fixFloat(float64(imgSize.X)*minRatio, backgroundSize.X), fixFloat(float64(imgSize.Y)*minRatio, backgroundSize.Y), imaging.CatmullRom)
- return imaging.PasteCenter(background, img)
- default:
- width, height := size.Width, size.Height
- if width == 0 && height != 0 {
- // size 50x0, source 100x200 => crop to 50x100
- width = int(float64(imgSize.X) / float64(imgSize.Y) * float64(size.Height))
- } else if height == 0 && width != 0 {
- // size 0x50, source 100x200 => crop to 25x50
- height = int(float64(imgSize.Y) / float64(imgSize.X) * float64(size.Width))
- } else if height == 0 && width == 0 {
- width, height = imgSize.X, imgSize.Y
- }
- return imaging.Thumbnail(img, width, height, imaging.Lanczos)
- }
- }
- func (imageHandler) Handle(media Media, file FileInterface, option *Option) (err error) {
- fileBytes, err := ioutil.ReadAll(file)
- if err != nil {
- return
- }
- fileSizes := media.GetFileSizes()
- originalFileSize := len(fileBytes)
- fileSizes["original"] = originalFileSize
- file.Seek(0, 0)
- err = media.Store(media.URL("original"), option, file)
- if err != nil {
- return
- }
- file.Seek(0, 0)
- format, err := GetImageFormat(media.URL())
- if err != nil {
- return
- }
- if *format == imaging.GIF {
- err = handleGIF(media, file, option, fileSizes, format)
- if err != nil {
- return
- }
- SetFileSizes(media, fileSizes)
- return
- }
- img, _, err := image.Decode(file)
- if err != nil {
- return
- }
- SetWeightHeight(media, img.Bounds().Dx(), img.Bounds().Dy())
- // Save cropped default image
- if cropOption := media.GetCropOption(DefaultSizeKey); cropOption != nil {
- var buffer bytes.Buffer
- imaging.Encode(&buffer, imaging.Crop(img, *cropOption), *format)
- fileSizes[DefaultSizeKey] = buffer.Len()
- media.Store(media.URL(), option, &buffer)
- } else {
- file.Seek(0, 0)
- // Save default image
- fileSizes[DefaultSizeKey] = originalFileSize
- media.Store(media.URL(), option, file)
- }
- // save sizes image
- for key, size := range media.GetSizes() {
- if key == DefaultSizeKey {
- continue
- }
- newImage := img
- if cropOption := media.GetCropOption(key); cropOption != nil {
- newImage = imaging.Crop(newImage, *cropOption)
- }
- var buffer bytes.Buffer
- imaging.Encode(&buffer, resizeImageTo(newImage, size, *format), *format)
- fileSizes[key] = buffer.Len()
- media.Store(media.URL(key), option, &buffer)
- }
- SetFileSizes(media, fileSizes)
- return
- }
- func handleGIF(media Media, file FileInterface, option *Option, fileSizes map[string]int, format *imaging.Format) error {
- var buffer bytes.Buffer
- g, err := gif.DecodeAll(file)
- if err != nil {
- return err
- }
- SetWeightHeight(media, g.Config.Width, g.Config.Height)
- if cropOption := media.GetCropOption(DefaultSizeKey); cropOption != nil {
- for i := range g.Image {
- img := imaging.Crop(g.Image[i], *cropOption)
- g.Image[i] = image.NewPaletted(img.Rect, g.Image[i].Palette)
- draw.Draw(g.Image[i], img.Rect, img, image.Pt(0, 0), draw.Src)
- if i == 0 {
- g.Config.Width = img.Rect.Dx()
- g.Config.Height = img.Rect.Dy()
- }
- }
- }
- gif.EncodeAll(&buffer, g)
- fileSizes[DefaultSizeKey] = buffer.Len()
- media.Store(media.URL(), option, &buffer)
- // save sizes image
- for key, size := range media.GetSizes() {
- if key == DefaultSizeKey {
- continue
- }
- file.Seek(0, 0)
- g, err := gif.DecodeAll(file)
- if err != nil {
- return err
- }
- for i := range g.Image {
- var img image.Image = g.Image[i]
- if cropOption := media.GetCropOption(key); cropOption != nil {
- img = imaging.Crop(g.Image[i], *cropOption)
- }
- img = resizeImageTo(img, size, *format)
- g.Image[i] = image.NewPaletted(image.Rect(0, 0, size.Width, size.Height), g.Image[i].Palette)
- draw.Draw(g.Image[i], image.Rect(0, 0, size.Width, size.Height), img, image.Pt(0, 0), draw.Src)
- }
- var buffer bytes.Buffer
- g.Config.Width = size.Width
- g.Config.Height = size.Height
- gif.EncodeAll(&buffer, g)
- fileSizes[key] = buffer.Len()
- media.Store(media.URL(key), option, &buffer)
- }
- return nil
- }
- func SetWeightHeight(media Media, width, height int) {
- result, _ := json.Marshal(map[string]int{"Width": width, "Height": height})
- media.Scan(string(result))
- }
- func SetFileSizes(media Media, fileSizes map[string]int) {
- result, _ := json.Marshal(map[string]map[string]int{"FileSizes": fileSizes})
- media.Scan(string(result))
- }
- func init() {
- RegisterMediaHandler("image_handler", imageHandler{})
- }
|