mirror of
https://github.com/gohugoio/hugo.git
synced 2025-02-04 01:52:06 +00:00
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
This commit is contained in:
parent
faa3159e5e
commit
799c654b0d
4 changed files with 79 additions and 20 deletions
|
@ -30,6 +30,7 @@ import (
|
|||
|
||||
// Importing image codecs for image.DecodeConfig
|
||||
"image"
|
||||
"image/draw"
|
||||
_ "image/gif"
|
||||
"image/jpeg"
|
||||
_ "image/png"
|
||||
|
@ -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,
|
||||
|
@ -117,6 +130,8 @@ type Image struct {
|
|||
|
||||
imaging *Imaging
|
||||
|
||||
format imaging.Format
|
||||
|
||||
hash string
|
||||
|
||||
*genericResource
|
||||
|
@ -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)}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -541,6 +569,7 @@ func (i *Image) clone() *Image {
|
|||
return &Image{
|
||||
imaging: i.imaging,
|
||||
hash: i.hash,
|
||||
format: i.format,
|
||||
genericResource: &g}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -19,6 +19,8 @@ import (
|
|||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
|
||||
"sync"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -258,6 +260,24 @@ func TestImageWithMetadata(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkResizeParallel(b *testing.B) {
|
||||
assert := require.New(b)
|
||||
img := fetchSunset(assert)
|
||||
|
|
|
@ -23,6 +23,8 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
|
@ -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
|
||||
}
|
||||
|
|
BIN
resource/testdata/gohugoio.png
vendored
Normal file
BIN
resource/testdata/gohugoio.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
Loading…
Reference in a new issue