123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- package vips
- import (
- "bytes"
- "io"
- "path"
- "strings"
- "github.com/disintegration/imaging"
- "github.com/qor5/admin/media"
- "github.com/theplant/bimg"
- )
- var (
- EnableGenerateWebp = false
- PNGtoWebpQuality = 90
- JPEGtoWebpQuality = 80
- GIFtoWebpQuality = 85
- JPEGQuality = 80
- PNGQuality = 90
- PNGCompression = 9
- )
- type Config struct {
- EnableGenerateWebp bool
- PNGtoWebpQuality int
- JPEGtoWebpQuality int
- JPEGQuality int
- PNGQuality int
- PNGCompression int
- }
- type bimgImageHandler struct{}
- func (bimgImageHandler) CouldHandle(media media.Media) bool {
- return media.IsImage()
- }
- // Crop & Resize
- func (bimgImageHandler) Handle(m media.Media, file media.FileInterface, option *media.Option) (err error) {
- buffer := new(bytes.Buffer)
- if _, err := io.Copy(buffer, file); err != nil {
- return err
- }
- fileSizes := m.GetFileSizes()
- fileSizes["original"] = buffer.Len()
- // Auto Rotate by EXIF
- img := copyImage(buffer.Bytes())
- metaData, _ := img.Metadata()
- media.SetWeightHeight(m, metaData.Size.Width, metaData.Size.Height)
- if metaData.EXIF.Orientation > 1 {
- rotatedBuf, err := img.AutoRotate()
- if err != nil {
- return err
- }
- buffer.Reset()
- if _, err := io.Copy(buffer, bytes.NewBuffer(rotatedBuf)); err != nil {
- return err
- }
- }
- // Save Original Image
- {
- if err = m.Store(m.URL("original"), option, bytes.NewReader(buffer.Bytes())); err != nil {
- return err
- }
- img := copyImage(buffer.Bytes())
- if err = generateWebp(m, option, bimg.Options{}, img, "original"); err != nil {
- return err
- }
- }
- // TODO support crop gif
- if isGif(m.URL()) {
- if err = m.Store(m.URL(), option, file); err != nil {
- return err
- }
- img := copyImage(buffer.Bytes())
- bimgOption := bimg.Options{Palette: true, Compression: PNGCompression}
- if err = generateWebp(m, option, bimgOption, img); err != nil {
- return err
- }
- for key, _ := range m.GetSizes() {
- if key == media.DefaultSizeKey {
- continue
- }
- img := copyImage(buffer.Bytes())
- if err = m.Store(m.URL(key), option, file); err != nil {
- return err
- }
- fileSizes[key] = buffer.Len()
- if err = generateWebp(m, option, bimgOption, img, key); err != nil {
- return err
- }
- }
- media.SetFileSizes(m, fileSizes)
- return
- }
- quality := getQualityByImageType(m.URL())
- // Handle default image
- {
- img := copyImage(buffer.Bytes())
- bimgOption := bimg.Options{Quality: quality, Palette: true, Compression: PNGCompression}
- // Crop original image if specified
- if cropOption := m.GetCropOption(media.DefaultSizeKey); cropOption != nil {
- options := bimg.Options{
- Quality: 100, // Don't compress twice
- Top: cropOption.Min.Y,
- Left: cropOption.Min.X,
- AreaWidth: cropOption.Max.X - cropOption.Min.X,
- AreaHeight: cropOption.Max.Y - cropOption.Min.Y,
- }
- if options.Top == 0 && options.Left == 0 {
- options.Top = -1
- }
- if _, err := img.Process(options); err != nil {
- return err
- }
- }
- copy := copyImage(img.Image())
- if buf, err := img.Process(bimgOption); err == nil {
- if err = m.Store(m.URL(), option, bytes.NewReader(buf)); err != nil {
- return err
- }
- fileSizes[media.DefaultSizeKey] = len(buf)
- } else {
- return err
- }
- if err = generateWebp(m, option, bimgOption, copy); err != nil {
- return err
- }
- }
- // Handle size images
- for key, size := range m.GetSizes() {
- if key == media.DefaultSizeKey {
- continue
- }
- img := copyImage(buffer.Bytes())
- if cropOption := m.GetCropOption(key); cropOption != nil {
- options := bimg.Options{
- Quality: 100, // Don't compress twice
- Top: cropOption.Min.Y,
- Left: cropOption.Min.X,
- AreaWidth: cropOption.Max.X - cropOption.Min.X,
- AreaHeight: cropOption.Max.Y - cropOption.Min.Y,
- }
- if options.Top == 0 && options.Left == 0 {
- options.Top = -1
- }
- if _, err := img.Process(options); err != nil {
- return err
- }
- }
- copy := copyImage(img.Image())
- bimgOption := bimg.Options{
- Width: size.Width,
- Height: size.Height,
- Quality: quality,
- Compression: PNGCompression,
- Palette: true,
- Enlarge: true,
- }
- // Process & Save size image
- if buf, err := img.Process(bimgOption); err == nil {
- if err = m.Store(m.URL(key), option, bytes.NewReader(buf)); err != nil {
- return err
- }
- fileSizes[key] = len(buf)
- } else {
- return err
- }
- if err = generateWebp(m, option, bimgOption, copy, key); err != nil {
- return err
- }
- }
- media.SetFileSizes(m, fileSizes)
- return
- }
- func generateWebp(m media.Media, option *media.Option, bimgOption bimg.Options, img *bimg.Image, size ...string) (err error) {
- if !EnableGenerateWebp {
- return
- }
- bimgOption.Type = bimg.WEBP
- bimgOption.Quality = getWebpQualityByImageType(m.URL())
- if buf, err := img.Process(bimgOption); err == nil {
- url := m.URL(size...)
- ext := path.Ext(url)
- extArr := strings.Split(ext, "?")
- i := strings.LastIndex(url, ext)
- webpUrl := url[:i] + strings.Replace(url[i:], extArr[0], ".webp", 1)
- m.Store(webpUrl, option, bytes.NewReader(buf))
- } else {
- return err
- }
- return
- }
- func copyImage(buffer []byte) (img *bimg.Image) {
- bs := make([]byte, len(buffer))
- copy(bs, buffer)
- img = bimg.NewImage(bs)
- return
- }
- func getQualityByImageType(url string) int {
- imgType, err := media.GetImageFormat(url)
- if err != nil {
- return 0
- }
- switch *imgType {
- case imaging.JPEG:
- return JPEGQuality
- case imaging.PNG:
- return PNGQuality
- }
- return 0
- }
- func getWebpQualityByImageType(url string) int {
- imgType, err := media.GetImageFormat(url)
- if err != nil {
- return 0
- }
- switch *imgType {
- case imaging.JPEG:
- return JPEGtoWebpQuality
- case imaging.PNG:
- return PNGtoWebpQuality
- case imaging.GIF:
- return GIFtoWebpQuality
- }
- return 0
- }
- func isGif(url string) bool {
- imgType, err := media.GetImageFormat(url)
- return err == nil && *imgType == imaging.GIF
- }
- func UseVips(cfg Config) {
- if cfg.EnableGenerateWebp {
- EnableGenerateWebp = true
- }
- if cfg.JPEGtoWebpQuality > 0 && cfg.JPEGtoWebpQuality <= 100 {
- JPEGtoWebpQuality = cfg.JPEGtoWebpQuality
- }
- if cfg.PNGtoWebpQuality > 0 && cfg.PNGtoWebpQuality <= 100 {
- PNGtoWebpQuality = cfg.PNGtoWebpQuality
- }
- if cfg.JPEGQuality > 0 && cfg.JPEGQuality <= 100 {
- JPEGQuality = cfg.JPEGQuality
- }
- if cfg.PNGQuality > 0 && cfg.PNGQuality <= 100 {
- PNGQuality = cfg.PNGQuality
- }
- if cfg.PNGCompression > 0 && cfg.PNGCompression <= 9 {
- PNGCompression = cfg.PNGCompression
- }
- bimg.VipsCacheSetMax(0)
- bimg.VipsCacheSetMaxMem(0)
- media.RegisterMediaHandler("image_handler", bimgImageHandler{})
- }
|