github.com/neohugo/neohugo@v0.123.8/tpl/images/images.go (about) 1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 // Package images provides template functions for manipulating images. 15 package images 16 17 import ( 18 "errors" 19 "image" 20 "sync" 21 22 "github.com/bep/overlayfs" 23 "github.com/neohugo/neohugo/resources/images" 24 25 // Importing image codecs for image.DecodeConfig 26 _ "image/gif" 27 _ "image/jpeg" 28 _ "image/png" 29 30 // Import webp codec 31 _ "golang.org/x/image/webp" 32 33 "github.com/neohugo/neohugo/deps" 34 "github.com/spf13/afero" 35 "github.com/spf13/cast" 36 ) 37 38 // New returns a new instance of the images-namespaced template functions. 39 func New(d *deps.Deps) *Namespace { 40 var readFileFs afero.Fs 41 42 // The docshelper script does not have or need all the dependencies set up. 43 if d.PathSpec != nil { 44 readFileFs = overlayfs.New(overlayfs.Options{ 45 Fss: []afero.Fs{ 46 d.PathSpec.BaseFs.Work, 47 d.PathSpec.BaseFs.Content.Fs, 48 }, 49 }) 50 } 51 52 return &Namespace{ 53 readFileFs: readFileFs, 54 Filters: &images.Filters{}, 55 cache: map[string]image.Config{}, 56 deps: d, 57 } 58 } 59 60 // Namespace provides template functions for the "images" namespace. 61 type Namespace struct { 62 *images.Filters 63 readFileFs afero.Fs 64 cacheMu sync.RWMutex 65 cache map[string]image.Config 66 67 deps *deps.Deps 68 } 69 70 // Config returns the image.Config for the specified path relative to the 71 // working directory. 72 func (ns *Namespace) Config(path any) (image.Config, error) { 73 filename, err := cast.ToStringE(path) 74 if err != nil { 75 return image.Config{}, err 76 } 77 78 if filename == "" { 79 return image.Config{}, errors.New("config needs a filename") 80 } 81 82 // Check cache for image config. 83 ns.cacheMu.RLock() 84 config, ok := ns.cache[filename] 85 ns.cacheMu.RUnlock() 86 87 if ok { 88 return config, nil 89 } 90 91 f, err := ns.readFileFs.Open(filename) 92 if err != nil { 93 return image.Config{}, err 94 } 95 defer f.Close() 96 97 config, _, err = image.DecodeConfig(f) 98 if err != nil { 99 return config, err 100 } 101 102 ns.cacheMu.Lock() 103 ns.cache[filename] = config 104 ns.cacheMu.Unlock() 105 106 return config, nil 107 } 108 109 // Filter applies the given filters to the image given as the last element in args. 110 func (ns *Namespace) Filter(args ...any) (images.ImageResource, error) { 111 if len(args) < 2 { 112 return nil, errors.New("must provide an image and one or more filters") 113 } 114 115 img := args[len(args)-1].(images.ImageResource) 116 filtersv := args[:len(args)-1] 117 118 return img.Filter(filtersv...) 119 }