mirror of
				https://github.com/gohugoio/hugo.git
				synced 2024-05-11 05:54:58 +00:00 
			
		
		
		
	When doing something like this with the same image from a partial used in, say, both the home page and the single page:
```bash
{{ with $img }}
{{ $big := .Fill "1024x512 top" }}
{{ $small := $big.Resize "512x" }}
{{ end }}
```
There would be timing issues making Hugo in some cases try to process the same image with the same instructions in parallel.
You would experience errors of type:
```bash
png: invalid format: not enough pixel data
```
This commit works around that by adding a mutex per image. This should also improve the performance, sligthly, as it avoids duplicate work.
The current workaround before this fix is to always operate on the original:
```bash
{{ with $img }}
{{ $big := .Fill "1024x512 top" }}
{{ $small := .Fill "512x256 top" }}
{{ end }}
```
Fixes #4404
		
	
		
			
				
	
	
		
			135 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			135 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package resource
 | 
						|
 | 
						|
import (
 | 
						|
	"path/filepath"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"image"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
	"runtime"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/gohugoio/hugo/helpers"
 | 
						|
	"github.com/gohugoio/hugo/hugofs"
 | 
						|
	"github.com/gohugoio/hugo/media"
 | 
						|
	"github.com/spf13/afero"
 | 
						|
	"github.com/spf13/viper"
 | 
						|
	"github.com/stretchr/testify/require"
 | 
						|
)
 | 
						|
 | 
						|
func newTestResourceSpec(assert *require.Assertions) *Spec {
 | 
						|
	return newTestResourceSpecForBaseURL(assert, "https://example.com/")
 | 
						|
}
 | 
						|
 | 
						|
func newTestResourceSpecForBaseURL(assert *require.Assertions, baseURL string) *Spec {
 | 
						|
	cfg := viper.New()
 | 
						|
	cfg.Set("baseURL", baseURL)
 | 
						|
	cfg.Set("resourceDir", "/res")
 | 
						|
 | 
						|
	imagingCfg := map[string]interface{}{
 | 
						|
		"resampleFilter": "linear",
 | 
						|
		"quality":        68,
 | 
						|
		"anchor":         "left",
 | 
						|
	}
 | 
						|
 | 
						|
	cfg.Set("imaging", imagingCfg)
 | 
						|
 | 
						|
	fs := hugofs.NewMem(cfg)
 | 
						|
 | 
						|
	s, err := helpers.NewPathSpec(fs, cfg)
 | 
						|
 | 
						|
	assert.NoError(err)
 | 
						|
 | 
						|
	spec, err := NewSpec(s, media.DefaultTypes)
 | 
						|
	assert.NoError(err)
 | 
						|
	return spec
 | 
						|
}
 | 
						|
 | 
						|
func newTestResourceOsFs(assert *require.Assertions) *Spec {
 | 
						|
	cfg := viper.New()
 | 
						|
	cfg.Set("baseURL", "https://example.com")
 | 
						|
 | 
						|
	workDir, err := ioutil.TempDir("", "hugores")
 | 
						|
 | 
						|
	if runtime.GOOS == "darwin" && !strings.HasPrefix(workDir, "/private") {
 | 
						|
		// To get the entry folder in line with the rest. This its a little bit
 | 
						|
		// mysterious, but so be it.
 | 
						|
		workDir = "/private" + workDir
 | 
						|
	}
 | 
						|
 | 
						|
	contentDir := "base"
 | 
						|
	cfg.Set("workingDir", workDir)
 | 
						|
	cfg.Set("contentDir", contentDir)
 | 
						|
	cfg.Set("resourceDir", filepath.Join(workDir, "res"))
 | 
						|
 | 
						|
	fs := hugofs.NewFrom(hugofs.Os, cfg)
 | 
						|
	fs.Destination = &afero.MemMapFs{}
 | 
						|
 | 
						|
	s, err := helpers.NewPathSpec(fs, cfg)
 | 
						|
 | 
						|
	assert.NoError(err)
 | 
						|
 | 
						|
	spec, err := NewSpec(s, media.DefaultTypes)
 | 
						|
	assert.NoError(err)
 | 
						|
	return spec
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func fetchSunset(assert *require.Assertions) *Image {
 | 
						|
	return fetchImage(assert, "sunset.jpg")
 | 
						|
}
 | 
						|
 | 
						|
func fetchImage(assert *require.Assertions, name string) *Image {
 | 
						|
	spec := newTestResourceSpec(assert)
 | 
						|
	return fetchImageForSpec(spec, assert, name)
 | 
						|
}
 | 
						|
 | 
						|
func fetchImageForSpec(spec *Spec, assert *require.Assertions, name string) *Image {
 | 
						|
	src, err := os.Open("testdata/" + name)
 | 
						|
	assert.NoError(err)
 | 
						|
 | 
						|
	workingDir := spec.Cfg.GetString("workingDir")
 | 
						|
	f := filepath.Join(workingDir, name)
 | 
						|
 | 
						|
	out, err := spec.Fs.Source.Create(f)
 | 
						|
	assert.NoError(err)
 | 
						|
	_, err = io.Copy(out, src)
 | 
						|
	out.Close()
 | 
						|
	src.Close()
 | 
						|
	assert.NoError(err)
 | 
						|
 | 
						|
	factory := func(s string) string {
 | 
						|
		return path.Join("/a", s)
 | 
						|
	}
 | 
						|
 | 
						|
	r, err := spec.NewResourceFromFilename(factory, "/public", f, name)
 | 
						|
	assert.NoError(err)
 | 
						|
	assert.IsType(&Image{}, r)
 | 
						|
	return r.(*Image)
 | 
						|
}
 | 
						|
 | 
						|
func assertFileCache(assert *require.Assertions, fs *hugofs.Fs, filename string, width, height int) {
 | 
						|
	f, err := fs.Source.Open(filepath.Join("/res/_gen/images", filename))
 | 
						|
	assert.NoError(err)
 | 
						|
	defer f.Close()
 | 
						|
 | 
						|
	config, _, err := image.DecodeConfig(f)
 | 
						|
	assert.NoError(err)
 | 
						|
 | 
						|
	assert.Equal(width, config.Width)
 | 
						|
	assert.Equal(height, config.Height)
 | 
						|
}
 | 
						|
 | 
						|
func writeSource(t testing.TB, fs *hugofs.Fs, filename, content string) {
 | 
						|
	writeToFs(t, fs.Source, filename, content)
 | 
						|
}
 | 
						|
 | 
						|
func writeToFs(t testing.TB, fs afero.Fs, filename, content string) {
 | 
						|
	if err := afero.WriteFile(fs, filepath.FromSlash(filename), []byte(content), 0755); err != nil {
 | 
						|
		t.Fatalf("Failed to write file: %s", err)
 | 
						|
	}
 | 
						|
}
 |