Skip to content

Commit

Permalink
resource: Preserve color palette for PNG images
Browse files Browse the repository at this point in the history
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 gohugoio#4416
  • Loading branch information
bep committed Feb 19, 2018
1 parent faa3159 commit a4fc700
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 20 deletions.
69 changes: 49 additions & 20 deletions resource/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (

// Importing image codecs for image.DecodeConfig
"image"
"image/draw"
_ "image/gif"
"image/jpeg"
_ "image/png"
Expand Down Expand Up @@ -65,15 +66,27 @@ const (
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,
Expand Down Expand Up @@ -117,6 +130,8 @@ type Image struct {

imaging *Imaging

format imaging.Format

hash string

*genericResource
Expand All @@ -137,6 +152,7 @@ func (i *Image) WithNewBase(base string) Resource {
return &Image{
imaging: i.imaging,
hash: i.hash,
format: i.format,
genericResource: i.genericResource.WithNewBase(base).(*genericResource)}
}

Expand Down Expand Up @@ -246,6 +262,15 @@ func (i *Image) doWithImageConfig(action, spec string, f func(src image.Image, c
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
Expand All @@ -255,7 +280,7 @@ func (i *Image) doWithImageConfig(action, spec string, f func(src image.Image, c

}

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
Expand All @@ -277,6 +302,14 @@ func (i imageConfig) key() string {
k += "_" + anchor
}

if v, ok := imageFormatsVersions[format]; ok {
k += "_" + strconv.Itoa(v)
}

if mainImageVersionNumber > 0 {
k += "_" + strconv.Itoa(mainImageVersionNumber)
}

return k
}

Expand Down Expand Up @@ -410,7 +443,8 @@ func (i *Image) decodeSource() (image.Image, error) {
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 {
Expand Down Expand Up @@ -464,12 +498,6 @@ func (i *Image) copyToDestination(src string) error {
}

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)

Expand Down Expand Up @@ -509,7 +537,7 @@ func (i *Image) encodeToDestinations(img image.Image, conf imageConfig, resource
w = file1
}

switch imgFormat {
switch i.format {
case imaging.JPEG:

var rgba *image.RGBA
Expand All @@ -530,7 +558,7 @@ func (i *Image) encodeToDestinations(img image.Image, conf imageConfig, resource
return jpeg.Encode(w, img, &jpeg.Options{Quality: quality})
}
default:
return imaging.Encode(w, img, imgFormat)
return imaging.Encode(w, img, i.format)
}

}
Expand All @@ -541,6 +569,7 @@ func (i *Image) clone() *Image {
return &Image{
imaging: i.imaging,
hash: i.hash,
format: i.format,
genericResource: &g}
}

Expand All @@ -555,7 +584,7 @@ func (i *Image) filenameFromConfig(conf imageConfig) string {
// 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
Expand Down
16 changes: 16 additions & 0 deletions resource/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,22 @@ func TestImageWithMetadata(t *testing.T) {

}

func TestImageResize8BitPNG(t *testing.T) {

assert := require.New(t)

image := fetchImage(assert, "gohugoio.png")

assert.Equal("/a/gohugoio.png", image.RelPermalink())
assert.Equal("image", image.ResourceType())

resized, err := image.Resize("800x")
assert.NoError(err)
assert.Equal("/a/gohugoio_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_800x0_resize_linear_1.png", resized.RelPermalink())
assert.Equal(800, resized.Width())

}

func BenchmarkResizeParallel(b *testing.B) {
assert := require.New(b)
img := fetchSunset(assert)
Expand Down
10 changes: 10 additions & 0 deletions resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"strings"
"sync"

"github.com/disintegration/imaging"

"github.com/spf13/cast"

"github.com/gobwas/glob"
Expand Down Expand Up @@ -297,8 +299,16 @@ func (r *Spec) newResource(
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
}
Expand Down
Binary file added resource/testdata/gohugoio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit a4fc700

Please sign in to comment.