mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
resources/images: Add $image.Colors
Which returns the most dominant colors of an image using a simple histogram method. Fixes #10307
This commit is contained in:
parent
08f0984f91
commit
a4028112e3
10 changed files with 90 additions and 0 deletions
|
@ -163,6 +163,19 @@ Sometimes it can be useful to create the filter chain once and then reuse it.
|
||||||
{{ $image2 := $image2.Filter $filters }}
|
{{ $image2 := $image2.Filter $filters }}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Colors
|
||||||
|
|
||||||
|
{{< new-in "0.104.0" >}}
|
||||||
|
|
||||||
|
`.Colors` returns a slice of hex string with the dominant colors in the image using a simple histogram method.
|
||||||
|
|
||||||
|
```go-html-template
|
||||||
|
{{ $colors := $image.Colors }}
|
||||||
|
```
|
||||||
|
|
||||||
|
This method is fast, but if you also scale down your images, it would be good for performance to extract the colors from the scaled down image.
|
||||||
|
|
||||||
|
|
||||||
### Exif
|
### Exif
|
||||||
|
|
||||||
Provides an [Exif] object containing image metadata.
|
Provides an [Exif] object containing image metadata.
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -91,6 +91,7 @@ require (
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.4.0 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.4.0 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.7.0 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.7.0 // indirect
|
||||||
github.com/aws/smithy-go v1.8.0 // indirect
|
github.com/aws/smithy-go v1.8.0 // indirect
|
||||||
|
github.com/cenkalti/dominantcolor v1.0.0 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
@ -108,6 +109,7 @@ require (
|
||||||
github.com/kr/pretty v0.3.0 // indirect
|
github.com/kr/pretty v0.3.0 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
|
||||||
|
github.com/marekm4/color-extractor v1.2.0 // indirect
|
||||||
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -184,6 +184,8 @@ github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=
|
||||||
github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
|
github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
|
||||||
github.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg=
|
github.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg=
|
||||||
github.com/bep/workers v1.0.0/go.mod h1:7kIESOB86HfR2379pwoMWNy8B50D7r99fRLUyPSNyCs=
|
github.com/bep/workers v1.0.0/go.mod h1:7kIESOB86HfR2379pwoMWNy8B50D7r99fRLUyPSNyCs=
|
||||||
|
github.com/cenkalti/dominantcolor v1.0.0 h1:MFLKUzcxQf65GRQdCcpcMlEFYvvy4Y51+eJ4bLpe4bM=
|
||||||
|
github.com/cenkalti/dominantcolor v1.0.0/go.mod h1:/fauwSWvIFhvyrHSOhqRwdnjZLETEl5ocyxCkakCI/Q=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
|
@ -437,6 +439,8 @@ github.com/magefile/mage v1.13.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXq
|
||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/marekm4/color-extractor v1.2.0 h1:DCU/FXg3PlAwig7W5PRZshiX5x38k0aNPTxYZ6/fZb0=
|
||||||
|
github.com/marekm4/color-extractor v1.2.0/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BAOp7w+NpwBdkJmpA=
|
||||||
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||||
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
|
||||||
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
|
||||||
|
|
|
@ -123,6 +123,10 @@ func (e *errorResource) Exif() *exif.ExifInfo {
|
||||||
panic(e.ResourceError)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *errorResource) Colors() ([]string, error) {
|
||||||
|
panic(e.ResourceError)
|
||||||
|
}
|
||||||
|
|
||||||
func (e *errorResource) DecodeImage() (image.Image, error) {
|
func (e *errorResource) DecodeImage() (image.Image, error) {
|
||||||
panic(e.ResourceError)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
color_extractor "github.com/marekm4/color-extractor"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/paths"
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
|
|
||||||
"github.com/disintegration/gift"
|
"github.com/disintegration/gift"
|
||||||
|
@ -64,6 +66,9 @@ type imageResource struct {
|
||||||
metaInitErr error
|
metaInitErr error
|
||||||
meta *imageMeta
|
meta *imageMeta
|
||||||
|
|
||||||
|
dominantColorInit sync.Once
|
||||||
|
dominantColors []string
|
||||||
|
|
||||||
baseResource
|
baseResource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +140,24 @@ func (i *imageResource) getExif() *exif.ExifInfo {
|
||||||
return i.meta.Exif
|
return i.meta.Exif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Colors returns a slice of the most dominant colors in an image
|
||||||
|
// using a simple histogram method.
|
||||||
|
func (i *imageResource) Colors() ([]string, error) {
|
||||||
|
var err error
|
||||||
|
i.dominantColorInit.Do(func() {
|
||||||
|
var img image.Image
|
||||||
|
img, err = i.DecodeImage()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
colors := color_extractor.ExtractColors(img)
|
||||||
|
for _, c := range colors {
|
||||||
|
i.dominantColors = append(i.dominantColors, images.ColorToHexString(c))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return i.dominantColors, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Clone is for internal use.
|
// Clone is for internal use.
|
||||||
func (i *imageResource) Clone() resource.Resource {
|
func (i *imageResource) Clone() resource.Resource {
|
||||||
gr := i.baseResource.Clone().(baseResource)
|
gr := i.baseResource.Clone().(baseResource)
|
||||||
|
|
|
@ -84,6 +84,10 @@ func TestImageTransformBasic(t *testing.T) {
|
||||||
c.Assert(img.Height(), qt.Equals, h)
|
c.Assert(img.Height(), qt.Equals, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
colors, err := image.Colors()
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
c.Assert(colors, qt.DeepEquals, []string{"#2d2f33", "#a49e93", "#d39e59", "#a76936", "#737a84", "#7c838b"})
|
||||||
|
|
||||||
c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.jpg")
|
c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.jpg")
|
||||||
c.Assert(image.ResourceType(), qt.Equals, "image")
|
c.Assert(image.ResourceType(), qt.Equals, "image")
|
||||||
assertWidthHeight(image, 900, 562)
|
assertWidthHeight(image, 900, 562)
|
||||||
|
|
|
@ -45,6 +45,14 @@ func ReplaceColorInPalette(c color.Color, p color.Palette) {
|
||||||
p[p.Index(c)] = c
|
p[p.Index(c)] = c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ColorToHexString converts a color to a hex string.
|
||||||
|
func ColorToHexString(c color.Color) string {
|
||||||
|
r, g, b, a := c.RGBA()
|
||||||
|
rgba := color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
|
||||||
|
return fmt.Sprintf("#%.2x%.2x%.2x", rgba.R, rgba.G, rgba.B)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func hexStringToColor(s string) (color.Color, error) {
|
func hexStringToColor(s string) (color.Color, error) {
|
||||||
s = strings.TrimPrefix(s, "#")
|
s = strings.TrimPrefix(s, "#")
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,30 @@ func TestHexStringToColor(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestColorToHexString(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
for _, test := range []struct {
|
||||||
|
arg color.Color
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{color.White, "#ffffff"},
|
||||||
|
{color.Black, "#000000"},
|
||||||
|
{color.RGBA{R: 0x42, G: 0x87, B: 0xf5, A: 0xff}, "#4287f5"},
|
||||||
|
} {
|
||||||
|
|
||||||
|
test := test
|
||||||
|
c.Run(test.expect, func(c *qt.C) {
|
||||||
|
c.Parallel()
|
||||||
|
|
||||||
|
result := ColorToHexString(test.arg)
|
||||||
|
|
||||||
|
c.Assert(result, qt.Equals, test.expect)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAddColorToPalette(t *testing.T) {
|
func TestAddColorToPalette(t *testing.T) {
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,10 @@ type ImageResourceOps interface {
|
||||||
// Exif returns an ExifInfo object containing Image metadata.
|
// Exif returns an ExifInfo object containing Image metadata.
|
||||||
Exif() *exif.ExifInfo
|
Exif() *exif.ExifInfo
|
||||||
|
|
||||||
|
// Colors returns a slice of the most dominant colors in an image
|
||||||
|
// using a simple histogram method.
|
||||||
|
Colors() ([]string, error)
|
||||||
|
|
||||||
// Internal
|
// Internal
|
||||||
DecodeImage() (image.Image, error)
|
DecodeImage() (image.Image, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,6 +213,10 @@ func (r *resourceAdapter) Exif() *exif.ExifInfo {
|
||||||
return r.getImageOps().Exif()
|
return r.getImageOps().Exif()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *resourceAdapter) Colors() ([]string, error) {
|
||||||
|
return r.getImageOps().Colors()
|
||||||
|
}
|
||||||
|
|
||||||
func (r *resourceAdapter) Key() string {
|
func (r *resourceAdapter) Key() string {
|
||||||
r.init(false, false)
|
r.init(false, false)
|
||||||
return r.target.(resource.Identifier).Key()
|
return r.target.(resource.Identifier).Key()
|
||||||
|
|
Loading…
Reference in a new issue