forked from go/golangs_learn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
177 lines
4.2 KiB
177 lines
4.2 KiB
package image
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"io/ioutil"
|
|
"optimus/backend/config"
|
|
"optimus/backend/jpeg"
|
|
"optimus/backend/png"
|
|
"optimus/backend/webp"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
|
|
"github.com/disintegration/imaging"
|
|
"github.com/muesli/smartcrop"
|
|
"github.com/muesli/smartcrop/nfnt"
|
|
"github.com/wailsapp/wails"
|
|
)
|
|
|
|
const (
|
|
fill = iota
|
|
fit
|
|
smart
|
|
)
|
|
|
|
var mimes = map[string]string{
|
|
"image/.jpg": "jpg",
|
|
"image/jpg": "jpg",
|
|
"image/jpeg": "jpg",
|
|
"image/png": "png",
|
|
"image/webp": "webp",
|
|
}
|
|
|
|
// File represents an image file.
|
|
type File struct {
|
|
Data []byte `json:"data"`
|
|
Ext string `json:"ext"`
|
|
ID string `json:"id"`
|
|
MimeType string `json:"type"`
|
|
Name string `json:"name"`
|
|
Size int64 `json:"size"`
|
|
ConvertedFile string
|
|
IsConverted bool
|
|
Image image.Image
|
|
Runtime *wails.Runtime
|
|
}
|
|
|
|
// Decode decodes the file's data based on its mime type.
|
|
func (f *File) Decode() error {
|
|
mime, err := getFileType(f.MimeType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch mime {
|
|
case "jpg":
|
|
f.Image, err = jpeg.DecodeJPEG(bytes.NewReader(f.Data))
|
|
case "png":
|
|
f.Image, err = png.DecodePNG(bytes.NewReader(f.Data))
|
|
case "webp":
|
|
f.Image, err = webp.DecodeWebp(bytes.NewReader(f.Data))
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetConvertedSize returns the size of the converted file.
|
|
func (f *File) GetConvertedSize() (int64, error) {
|
|
if f.ConvertedFile == "" {
|
|
return 0, errors.New("file has no converted file")
|
|
}
|
|
s, err := os.Stat(f.ConvertedFile)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return s.Size(), nil
|
|
}
|
|
|
|
// GetSavings returns the delta between original and converted file size.
|
|
func (f *File) GetSavings() (int64, error) {
|
|
c, err := f.GetConvertedSize()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return f.Size - c, nil
|
|
}
|
|
|
|
// Write saves a file to disk based on the encoding target.
|
|
func (f *File) Write(c *config.Config) error {
|
|
// TODO resizing should probably be in its own method
|
|
if c.App.Sizes != nil {
|
|
for _, r := range c.App.Sizes {
|
|
if r.Height <= 0 || r.Width <= 0 {
|
|
f.Runtime.Events.Emit("notify", map[string]interface{}{
|
|
"msg": fmt.Sprintf("Invalid image size: %s", r.String()),
|
|
"type": "warn",
|
|
})
|
|
continue
|
|
}
|
|
var i image.Image
|
|
var s string
|
|
switch r.Strategy {
|
|
case fill:
|
|
i = imaging.Fill(f.Image, r.Width, r.Height, imaging.Center, imaging.Lanczos)
|
|
s = r.String()
|
|
case fit:
|
|
i = imaging.Fit(f.Image, r.Width, r.Height, imaging.Lanczos)
|
|
s = fmt.Sprintf("%dx%d", i.Bounds().Max.X, i.Bounds().Max.Y)
|
|
case smart:
|
|
analyzer := smartcrop.NewAnalyzer(nfnt.NewDefaultResizer())
|
|
crop, err := analyzer.FindBestCrop(f.Image, r.Width, r.Height)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
croppedImg := f.Image.(SubImager).SubImage(crop)
|
|
i = imaging.Resize(croppedImg, r.Width, r.Height, imaging.Lanczos)
|
|
s = fmt.Sprintf("%dx%d", i.Bounds().Max.X, i.Bounds().Max.Y)
|
|
}
|
|
buf, err := encToBuf(i, c.App)
|
|
dest := path.Join(c.App.OutDir, c.App.Prefix+f.Name+"--"+s+c.App.Suffix+"."+c.App.Target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = ioutil.WriteFile(dest, buf.Bytes(), 0666); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
buf, err := encToBuf(f.Image, c.App)
|
|
dest := path.Join(c.App.OutDir, c.App.Prefix+f.Name+c.App.Suffix+"."+c.App.Target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = ioutil.WriteFile(dest, buf.Bytes(), 0666); err != nil {
|
|
return err
|
|
}
|
|
f.ConvertedFile = filepath.Clean(dest)
|
|
f.IsConverted = true
|
|
return nil
|
|
}
|
|
|
|
// encToBuf encodes an image to a buffer using the configured target.
|
|
func encToBuf(i image.Image, a *config.App) (*bytes.Buffer, error) {
|
|
var b bytes.Buffer
|
|
var err error
|
|
switch a.Target {
|
|
case "jpg":
|
|
b, err = jpeg.EncodeJPEG(i, a.JpegOpt)
|
|
case "png":
|
|
b, err = png.EncodePNG(i, a.PngOpt)
|
|
case "webp":
|
|
b, err = webp.EncodeWebp(i, a.WebpOpt)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &b, nil
|
|
}
|
|
|
|
// getFileType returns the file's type based on the given mime type.
|
|
func getFileType(t string) (string, error) {
|
|
m, prs := mimes[t]
|
|
if !prs {
|
|
_ = errors.New("unsupported file type:" + t)
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// SubImager handles creating a subimage from an image rect.
|
|
type SubImager interface {
|
|
SubImage(r image.Rectangle) image.Image
|
|
}
|
|
|