mirror of
https://github.com/gohugoio/hugo.git
synced 2024-07-02 15:20:28 +00:00
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> ```
This commit is contained in:
parent
950034db5c
commit
a49f838cd0
|
@ -356,7 +356,7 @@ e.g.
|
||||||
{{ .Content }}
|
{{ .Content }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
### readDir
|
### readDir
|
||||||
|
|
||||||
|
@ -372,6 +372,16 @@ Reads a file from disk and converts it into a string. Note that the filename mus
|
||||||
|
|
||||||
`{{readFile "README.txt"}}` → `"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
|
## Math
|
||||||
|
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
|
|
|
@ -120,12 +120,14 @@ func (h *HugoSites) getNodes(nodeID string) Nodes {
|
||||||
return Nodes{}
|
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() {
|
func (h *HugoSites) reset() {
|
||||||
h.nodeMap = make(map[string]Nodes)
|
h.nodeMap = make(map[string]Nodes)
|
||||||
for i, s := range h.Sites {
|
for i, s := range h.Sites {
|
||||||
h.Sites[i] = s.reset()
|
h.Sites[i] = s.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tpl.ResetCaches()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HugoSites) reCreateFromConfig() error {
|
func (h *HugoSites) reCreateFromConfig() error {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"image"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -45,6 +46,11 @@ import (
|
||||||
"github.com/spf13/hugo/hugofs"
|
"github.com/spf13/hugo/hugofs"
|
||||||
jww "github.com/spf13/jwalterweatherman"
|
jww "github.com/spf13/jwalterweatherman"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
// Importing image codecs for image.DecodeConfig
|
||||||
|
_ "image/gif"
|
||||||
|
_ "image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -364,6 +370,63 @@ func intersect(l1, l2 interface{}) (interface{}, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
// in returns whether v is in the set l. l may be an array or slice.
|
||||||
func in(l interface{}, v interface{}) bool {
|
func in(l interface{}, v interface{}) bool {
|
||||||
lv := reflect.ValueOf(l)
|
lv := reflect.ValueOf(l)
|
||||||
|
@ -1991,6 +2054,7 @@ func initFuncMap() {
|
||||||
"htmlEscape": htmlEscape,
|
"htmlEscape": htmlEscape,
|
||||||
"htmlUnescape": htmlUnescape,
|
"htmlUnescape": htmlUnescape,
|
||||||
"humanize": humanize,
|
"humanize": humanize,
|
||||||
|
"imageConfig": imageConfig,
|
||||||
"in": in,
|
"in": in,
|
||||||
"index": index,
|
"index": index,
|
||||||
"int": func(v interface{}) (int, error) { return cast.ToIntE(v) },
|
"int": func(v interface{}) (int, error) { return cast.ToIntE(v) },
|
||||||
|
|
|
@ -19,6 +19,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -596,6 +599,109 @@ func TestDictionary(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIn(t *testing.T) {
|
func TestIn(t *testing.T) {
|
||||||
for i, this := range []struct {
|
for i, this := range []struct {
|
||||||
v1 interface{}
|
v1 interface{}
|
||||||
|
|
Loading…
Reference in a new issue