shithub: hugo

Download patch

ref: 799c654b0d39ec869c2da24d41de3636eb7157f0
parent: faa3159e5e09e2a95cbef4dca5fcb9c8e2b41bf4
author: Bjørn Erik Pedersen <[email protected]>
date: Mon Feb 19 11:34:49 EST 2018

resource: Preserve color palette for PNG images

This commit will force a reprocessing of PNG images with new names, so it is adviced to run a `hugo --gc` to remove stale files.

Fixes #4416

--- a/resource/image.go
+++ b/resource/image.go
@@ -30,6 +30,7 @@
 
 	// Importing image codecs for image.DecodeConfig
 	"image"
+	"image/draw"
 	_ "image/gif"
 	"image/jpeg"
 	_ "image/png"
@@ -65,16 +66,28 @@
 	defaultResampleFilter = "box"
 )
 
-var imageFormats = map[string]imaging.Format{
-	".jpg":  imaging.JPEG,
-	".jpeg": imaging.JPEG,
-	".png":  imaging.PNG,
-	".tif":  imaging.TIFF,
-	".tiff": imaging.TIFF,
-	".bmp":  imaging.BMP,
-	".gif":  imaging.GIF,
-}
+var (
+	imageFormats = map[string]imaging.Format{
+		".jpg":  imaging.JPEG,
+		".jpeg": imaging.JPEG,
+		".png":  imaging.PNG,
+		".tif":  imaging.TIFF,
+		".tiff": imaging.TIFF,
+		".bmp":  imaging.BMP,
+		".gif":  imaging.GIF,
+	}
 
+	// Add or increment if changes to an image format's processing requires
+	// re-generation.
+	imageFormatsVersions = map[imaging.Format]int{
+		imaging.PNG: 1, // 1: Add proper palette handling
+	}
+
+	// Increment to mark all processed images as stale. Only use when absolutely needed.
+	// See the finer grained smartCropVersionNumber and imageFormatsVersions.
+	mainImageVersionNumber = 0
+)
+
 var anchorPositions = map[string]imaging.Anchor{
 	strings.ToLower("Center"):      imaging.Center,
 	strings.ToLower("TopLeft"):     imaging.TopLeft,
@@ -117,6 +130,8 @@
 
 	imaging *Imaging
 
+	format imaging.Format
+
 	hash string
 
 	*genericResource
@@ -137,6 +152,7 @@
 	return &Image{
 		imaging:         i.imaging,
 		hash:            i.hash,
+		format:          i.format,
 		genericResource: i.genericResource.WithNewBase(base).(*genericResource)}
 }
 
@@ -246,6 +262,15 @@
 			return ci, &os.PathError{Op: errOp, Path: errPath, Err: err}
 		}
 
+		if i.format == imaging.PNG {
+			// Apply the colour palette from the source
+			if paletted, ok := src.(*image.Paletted); ok {
+				tmp := image.NewPaletted(converted.Bounds(), paletted.Palette)
+				draw.Src.Draw(tmp, tmp.Bounds(), converted, converted.Bounds().Min)
+				converted = tmp
+			}
+		}
+
 		b := converted.Bounds()
 		ci.config = image.Config{Width: b.Max.X, Height: b.Max.Y}
 		ci.configLoaded = true
@@ -255,7 +280,7 @@
 
 }
 
-func (i imageConfig) key() string {
+func (i imageConfig) key(format imaging.Format) string {
 	k := strconv.Itoa(i.Width) + "x" + strconv.Itoa(i.Height)
 	if i.Action != "" {
 		k += "_" + i.Action
@@ -277,6 +302,14 @@
 		k += "_" + anchor
 	}
 
+	if v, ok := imageFormatsVersions[format]; ok {
+		k += "_" + strconv.Itoa(v)
+	}
+
+	if mainImageVersionNumber > 0 {
+		k += "_" + strconv.Itoa(mainImageVersionNumber)
+	}
+
 	return k
 }
 
@@ -410,7 +443,8 @@
 		return nil, err
 	}
 	defer file.Close()
-	return imaging.Decode(file)
+	img, _, err := image.Decode(file)
+	return img, err
 }
 
 func (i *Image) copyToDestination(src string) error {
@@ -464,13 +498,7 @@
 }
 
 func (i *Image) encodeToDestinations(img image.Image, conf imageConfig, resourceCacheFilename, filename string) error {
-	ext := strings.ToLower(helpers.Ext(filename))
 
-	imgFormat, ok := imageFormats[ext]
-	if !ok {
-		return imaging.ErrUnsupportedFormat
-	}
-
 	target := filepath.Join(i.absPublishDir, filename)
 
 	file1, err := i.spec.Fs.Destination.Create(target)
@@ -509,7 +537,7 @@
 		w = file1
 	}
 
-	switch imgFormat {
+	switch i.format {
 	case imaging.JPEG:
 
 		var rgba *image.RGBA
@@ -530,7 +558,7 @@
 			return jpeg.Encode(w, img, &jpeg.Options{Quality: quality})
 		}
 	default:
-		return imaging.Encode(w, img, imgFormat)
+		return imaging.Encode(w, img, i.format)
 	}
 
 }
@@ -541,6 +569,7 @@
 	return &Image{
 		imaging:         i.imaging,
 		hash:            i.hash,
+		format:          i.format,
 		genericResource: &g}
 }
 
@@ -555,7 +584,7 @@
 	// Do not change for no good reason.
 	const md5Threshold = 100
 
-	key := conf.key()
+	key := conf.key(i.format)
 
 	// It is useful to have the key in clear text, but when nesting transforms, it
 	// can easily be too long to read, and maybe even too long
--- a/resource/image_test.go
+++ b/resource/image_test.go
@@ -19,6 +19,8 @@
 	"strconv"
 	"testing"
 
+	"github.com/disintegration/imaging"
+
 	"sync"
 
 	"github.com/stretchr/testify/require"
@@ -255,6 +257,24 @@
 	resized, err := image.Resize("200x")
 	assert.NoError(err)
 	assert.Equal("Sunset #1", resized.Name())
+
+}
+
+func TestImageResize8BitPNG(t *testing.T) {
+
+	assert := require.New(t)
+
+	image := fetchImage(assert, "gohugoio.png")
+
+	assert.Equal(imaging.PNG, image.format)
+	assert.Equal("/a/gohugoio.png", image.RelPermalink())
+	assert.Equal("image", image.ResourceType())
+
+	resized, err := image.Resize("800x")
+	assert.NoError(err)
+	assert.Equal(imaging.PNG, resized.format)
+	assert.Equal("/a/gohugoio_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_800x0_resize_linear_1.png", resized.RelPermalink())
+	assert.Equal(800, resized.Width())
 
 }
 
--- a/resource/resource.go
+++ b/resource/resource.go
@@ -23,6 +23,8 @@
 	"strings"
 	"sync"
 
+	"github.com/disintegration/imaging"
+
 	"github.com/spf13/cast"
 
 	"github.com/gobwas/glob"
@@ -297,8 +299,16 @@
 			return nil, err
 		}
 
+		ext := strings.ToLower(helpers.Ext(absSourceFilename))
+
+		imgFormat, ok := imageFormats[ext]
+		if !ok {
+			return nil, imaging.ErrUnsupportedFormat
+		}
+
 		return &Image{
 			hash:            hash,
+			format:          imgFormat,
 			imaging:         r.imaging,
 			genericResource: gr}, nil
 	}
binary files /dev/null b/resource/testdata/gohugoio.png differ