package filter import ( "crypto/md5" "errors" "fmt" "image" "net/http" "net/url" "os" "path" "strconv" "strings" "gitbase.de/apairon/mark2web" "gitbase.de/apairon/mark2web/helper" "github.com/disintegration/imaging" "github.com/flosch/pongo2" ) func parseImageParams(str string) (*mark2web.ImagingConfig, error) { p := mark2web.ImagingConfig{} if str == "" { helper.Merge(&p, mark2web.CurrentPathConfig.Imaging) // Filename and Format are only valid for current image p.Filename = "" p.Format = "" return &p, nil } for _, s := range strings.Split(str, ",") { e := strings.Split(s, "=") if len(e) < 2 { return nil, fmt.Errorf("invalid image parameter: %s", s) } var err error switch e[0] { case "w": p.Width, err = strconv.Atoi(e[1]) case "h": p.Height, err = strconv.Atoi(e[1]) case "f": p.Filename = e[1] case "t": p.TargetDir = e[1] case "p": p.Process = e[1] case "a": p.Anchor = e[1] case "q": p.Quality, err = strconv.Atoi(e[1]) if p.Quality < 0 || p.Quality > 100 { err = errors.New("q= must be between 1 and 100") } default: return nil, fmt.Errorf("invalid image parameter: %s", s) } if err != nil { return nil, fmt.Errorf("could not convert image parameter to correct value type for '%s': %s", s, err) } } return &p, nil } func getImageFromURL(url string) (image.Image, string, error) { resp, err := http.Get(url) if err != nil { return nil, "", fmt.Errorf("could not get url '%s': %s", url, err) } img, format, err := image.Decode(resp.Body) if err != nil { return nil, "", fmt.Errorf("could read body from url '%s': %s", url, err) } return img, format, nil } // ImageProcessFilter read the image url and process parameters and saves the resized/processed image // param: w=WITDH,h=HEIGHT func ImageProcessFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { imgSource := in.String() p, err := parseImageParams(param.String()) if err != nil { return nil, &pongo2.Error{ Sender: "filter:image_resize", OrigError: err, } } if p == nil { return nil, &pongo2.Error{ Sender: "filter:image_resize", OrigError: errors.New("no imaging config defined"), } } var img image.Image if p.Process == "" { p.Process = "resize" } filePrefix := fmt.Sprintf( "%s_%dx%d_q%03d", p.Process, p.Width, p.Height, p.Quality, ) if strings.HasPrefix(imgSource, "http://") || strings.HasPrefix(imgSource, "https://") { // remote file img, p.Format, err = getImageFromURL(imgSource) if err != nil { return nil, &pongo2.Error{ Sender: "filter:image_resize", OrigError: fmt.Errorf("could not open image '%s': %s", imgSource, err), } } // build filename if p.Filename == "" { var fBase string if u, _ := url.Parse(imgSource); u != nil { fBase = strings.Split(path.Base(u.Path), ".")[0] } p.Filename = fmt.Sprintf( "%s_%x_%s.%s", filePrefix, md5.Sum([]byte(imgSource)), fBase, p.Format, ) } } else { // local file imgSource = mark2web.ResolveInputPath(imgSource) if p.Filename == "" { p.Filename = fmt.Sprintf( "%s_%s", filePrefix, path.Base(imgSource), ) } } var imgTarget string if p.TargetDir != "" { imgTarget = mark2web.ResolveOutputPath( path.Clean(p.TargetDir) + "/" + p.Filename, ) pt := path.Dir(imgTarget) if _, err := os.Stat(pt); os.IsNotExist(err) { helper.Log.Infof("create image target dir: %s", pt) if err := os.MkdirAll(pt, 0755); err != nil { return nil, &pongo2.Error{ Sender: "filter:image_resize", OrigError: fmt.Errorf("could not create image target dir '%s': %s", pt, err), } } } p.Filename = mark2web.ResolveNavPath(p.TargetDir + "/" + p.Filename) } else { imgTarget = mark2web.ResolveOutputPath(p.Filename) } if f, err := os.Stat(imgTarget); err == nil && !f.IsDir() { helper.Log.Noticef("skipped processing image from %s to %s, file already exists", imgSource, imgTarget) } else { helper.Log.Noticef("processing image from %s to %s", imgSource, imgTarget) if strings.HasPrefix(imgSource, "http://") || strings.HasPrefix(imgSource, "https://") { // webrequest before finding target filename, because of file format in filename } else { img, err = imaging.Open(imgSource, imaging.AutoOrientation(true)) if err != nil { return nil, &pongo2.Error{ Sender: "filter:image_resize", OrigError: fmt.Errorf("could not open image '%s': %s", imgSource, err), } } } switch p.Process { case "resize": img = imaging.Resize(img, p.Width, p.Height, imaging.Lanczos) case "fit": img = imaging.Fit(img, p.Width, p.Height, imaging.Lanczos) case "fill": var anchor imaging.Anchor switch strings.ToLower(p.Anchor) { case "": fallthrough case "center": anchor = imaging.Center case "topleft": anchor = imaging.TopLeft case "top": anchor = imaging.Top case "topright": anchor = imaging.TopRight case "left": anchor = imaging.Left case "right": anchor = imaging.Right case "bottomleft": anchor = imaging.BottomLeft case "bottom": anchor = imaging.Bottom case "bottomright": anchor = imaging.BottomRight default: return nil, &pongo2.Error{ Sender: "filter:image_resize", OrigError: fmt.Errorf("unknown anchor a=%s definition", p.Anchor), } } img = imaging.Fill(img, p.Width, p.Height, anchor, imaging.Lanczos) default: return nil, &pongo2.Error{ Sender: "filter:image_resize", OrigError: fmt.Errorf("invalid p parameter '%s'", p.Process), } } var encodeOptions = make([]imaging.EncodeOption, 0) if p.Quality > 0 { encodeOptions = append(encodeOptions, imaging.JPEGQuality(p.Quality)) } err = imaging.Save(img, imgTarget, encodeOptions...) if err != nil { return nil, &pongo2.Error{ Sender: "filter:image_resize", OrigError: fmt.Errorf("could save image '%s': %s", imgTarget, err), } } } return pongo2.AsValue(mark2web.ResolveNavPath(p.Filename)), nil }