ref: a49f838cd03f89aa9eb61584c510177fa5cbd3bd
parent: 950034db5cca6df75a2cb39c5e2c2e66dc41c534
author: Tristan Rice <rice@fn.lc>
date: Tue Nov 15 23:00:45 EST 2016
tpl: Add imageConfig function
Add imageConfig function which calls image.DecodeConfig and returns the height, width and color mode of the image. (#2677)
This allows for more advanced image shortcodes and templates such as those required by AMP.
layouts/shortcodes/amp-img.html
```
{{ $src := .Get "src" }}
{{ $config := imageConfig (printf "/static/%s" $src) }}
<amp-img src="{{$src}}"
height="{{$config.Height}}"
width="{{$config.Width}}"
layout="responsive">
</amp-img>
```
--- a/docs/content/templates/functions.md
+++ b/docs/content/templates/functions.md
@@ -356,7 +356,7 @@
{{ .Content }} {{ end }}-## Files
+## Files
### readDir
@@ -371,6 +371,16 @@
So, if you have a file with the name `README.txt` in the root of your project with the content `Hugo Rocks!`:
`{{readFile "README.txt"}}` → `"Hugo Rocks!"`+
+### imageConfig
+Parses the image and returns the height, width and color model.
+
+e.g.
+```
+{{ with (imageConfig "favicon.ico") }}+favicon.ico: {{.Width}} x {{.Height}}+{{ end }}+```
## Math
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -120,12 +120,14 @@
return Nodes{}}
-// Reset resets the sites, making it ready for a full rebuild.
+// Reset resets the sites and template caches, making it ready for a full rebuild.
func (h *HugoSites) reset() {h.nodeMap = make(map[string]Nodes)
for i, s := range h.Sites {h.Sites[i] = s.reset()
}
+
+ tpl.ResetCaches()
}
func (h *HugoSites) reCreateFromConfig() error {--- a/tpl/template_funcs.go
+++ b/tpl/template_funcs.go
@@ -26,6 +26,7 @@
"fmt"
"html"
"html/template"
+ "image"
"math/rand"
"net/url"
"os"
@@ -45,6 +46,11 @@
"github.com/spf13/hugo/hugofs"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
+
+ // Importing image codecs for image.DecodeConfig
+ _ "image/gif"
+ _ "image/jpeg"
+ _ "image/png"
)
var (
@@ -364,6 +370,63 @@
}
}
+// ResetCaches resets all caches that might be used during build.
+func ResetCaches() {+ resetImageConfigCache()
+}
+
+// imageConfigCache is a lockable cache for image.Config objects. It must be
+// locked before reading or writing to config.
+var imageConfigCache struct {+ sync.RWMutex
+ config map[string]image.Config
+}
+
+// resetImageConfigCache initializes and resets the imageConfig cache for the
+// imageConfig template function. This should be run once before every batch of
+// template renderers so the cache is cleared for new data.
+func resetImageConfigCache() {+ imageConfigCache.Lock()
+ defer imageConfigCache.Unlock()
+
+ imageConfigCache.config = map[string]image.Config{}+}
+
+// imageConfig returns the image.Config for the specified path relative to the
+// working directory. resetImageConfigCache must be run beforehand.
+func imageConfig(path interface{}) (image.Config, error) {+ filename, err := cast.ToStringE(path)
+ if err != nil {+ return image.Config{}, err+ }
+
+ if filename == "" {+ return image.Config{}, errors.New("imageConfig needs a filename")+ }
+
+ // Check cache for image config.
+ imageConfigCache.RLock()
+ config, ok := imageConfigCache.config[filename]
+ imageConfigCache.RUnlock()
+
+ if ok {+ return config, nil
+ }
+
+ f, err := hugofs.WorkingDir().Open(filename)
+ if err != nil {+ return image.Config{}, err+ }
+
+ config, _, err = image.DecodeConfig(f)
+
+ imageConfigCache.Lock()
+ imageConfigCache.config[filename] = config
+ imageConfigCache.Unlock()
+
+ return config, err
+}
+
// in returns whether v is in the set l. l may be an array or slice.
func in(l interface{}, v interface{}) bool {lv := reflect.ValueOf(l)
@@ -1991,6 +2054,7 @@
"htmlEscape": htmlEscape,
"htmlUnescape": htmlUnescape,
"humanize": humanize,
+ "imageConfig": imageConfig,
"in": in,
"index": index,
"int": func(v interface{}) (int, error) { return cast.ToIntE(v) },--- a/tpl/template_funcs_test.go
+++ b/tpl/template_funcs_test.go
@@ -19,6 +19,9 @@
"errors"
"fmt"
"html/template"
+ "image"
+ "image/color"
+ "image/png"
"math/rand"
"path"
"path/filepath"
@@ -593,6 +596,109 @@
t.Errorf("[%d] got %v but expected %v", i, r, this.expectedValue)}
}
+ }
+}
+
+func blankImage(width, height int) []byte {+ var buf bytes.Buffer
+ img := image.NewRGBA(image.Rect(0, 0, width, height))
+ if err := png.Encode(&buf, img); err != nil {+ panic(err)
+ }
+ return buf.Bytes()
+}
+
+func TestImageConfig(t *testing.T) {+ viper.Reset()
+ defer viper.Reset()
+
+ workingDir := "/home/hugo"
+
+ viper.Set("workingDir", workingDir)+
+ fs := &afero.MemMapFs{}+ hugofs.InitFs(fs)
+
+ for i, this := range []struct {+ resetCache bool
+ path string
+ input []byte
+ expected image.Config
+ }{+ {+ resetCache: true,
+ path: "a.png",
+ input: blankImage(10, 10),
+ expected: image.Config{+ Width: 10,
+ Height: 10,
+ ColorModel: color.NRGBAModel,
+ },
+ },
+ {+ resetCache: false,
+ path: "b.png",
+ input: blankImage(20, 15),
+ expected: image.Config{+ Width: 20,
+ Height: 15,
+ ColorModel: color.NRGBAModel,
+ },
+ },
+ {+ resetCache: false,
+ path: "a.png",
+ input: blankImage(20, 15),
+ expected: image.Config{+ Width: 10,
+ Height: 10,
+ ColorModel: color.NRGBAModel,
+ },
+ },
+ {+ resetCache: true,
+ path: "a.png",
+ input: blankImage(20, 15),
+ expected: image.Config{+ Width: 20,
+ Height: 15,
+ ColorModel: color.NRGBAModel,
+ },
+ },
+ } {+ afero.WriteFile(fs, filepath.Join(workingDir, this.path), this.input, 0755)
+
+ if this.resetCache {+ resetImageConfigCache()
+ }
+
+ result, err := imageConfig(this.path)
+ if err != nil {+ t.Errorf("imageConfig returned error: %s", err)+ }
+
+ if !reflect.DeepEqual(result, this.expected) {+ t.Errorf("[%d] imageConfig: expected '%v', got '%v'", i, this.expected, result)+ }
+
+ if len(imageConfigCache.config) == 0 {+ t.Error("imageConfigCache should have at least 1 item")+ }
+ }
+
+ if _, err := imageConfig(t); err == nil {+ t.Error("Expected error from imageConfig when passed invalid path")+ }
+
+ if _, err := imageConfig("non-existant.png"); err == nil {+ t.Error("Expected error from imageConfig when passed non-existant file")+ }
+
+ // test cache clearing
+ ResetCaches()
+
+ if len(imageConfigCache.config) != 0 {+ t.Error("ResetCaches should have cleared imageConfigCache")}
}
--
⑨