diff options
author | Bjørn Erik Pedersen <[email protected]> | 2022-09-21 16:24:54 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <[email protected]> | 2022-09-22 15:57:35 +0200 |
commit | a4028112e340b20909746daca0f8cabb09cbf28f (patch) | |
tree | 3952d26c1d6fd4ac543617272b7a0311cd119339 | |
parent | 08f0984f918bb97d35c1765d3b94b1a9a1a3b9cd (diff) | |
download | hugo-a4028112e340b20909746daca0f8cabb09cbf28f.tar.gz hugo-a4028112e340b20909746daca0f8cabb09cbf28f.zip |
resources/images: Add $image.Colors
Which returns the most dominant colors of an image using a simple histogram method.
Fixes #10307
-rw-r--r-- | docs/content/en/content-management/image-processing/index.md | 13 | ||||
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | go.sum | 4 | ||||
-rw-r--r-- | resources/errorResource.go | 4 | ||||
-rw-r--r-- | resources/image.go | 23 | ||||
-rw-r--r-- | resources/image_test.go | 4 | ||||
-rw-r--r-- | resources/images/color.go | 8 | ||||
-rw-r--r-- | resources/images/color_test.go | 24 | ||||
-rw-r--r-- | resources/images/image_resource.go | 4 | ||||
-rw-r--r-- | resources/transform.go | 4 |
10 files changed, 90 insertions, 0 deletions
diff --git a/docs/content/en/content-management/image-processing/index.md b/docs/content/en/content-management/image-processing/index.md index f2748f5db..d99ea7846 100644 --- a/docs/content/en/content-management/image-processing/index.md +++ b/docs/content/en/content-management/image-processing/index.md @@ -163,6 +163,19 @@ Sometimes it can be useful to create the filter chain once and then reuse it. {{ $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 Provides an [Exif] object containing image metadata. @@ -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/sts v1.7.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/dlclark/regexp2 v1.4.0 // 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/text v0.2.0 // 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-runewidth v0.0.9 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect @@ -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/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg= 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.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 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-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= 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/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= diff --git a/resources/errorResource.go b/resources/errorResource.go index 81375cc48..e9411c3db 100644 --- a/resources/errorResource.go +++ b/resources/errorResource.go @@ -123,6 +123,10 @@ func (e *errorResource) Exif() *exif.ExifInfo { panic(e.ResourceError) } +func (e *errorResource) Colors() ([]string, error) { + panic(e.ResourceError) +} + func (e *errorResource) DecodeImage() (image.Image, error) { panic(e.ResourceError) } diff --git a/resources/image.go b/resources/image.go index 8551cc2ab..ea8a21156 100644 --- a/resources/image.go +++ b/resources/image.go @@ -30,6 +30,8 @@ import ( "strings" "sync" + color_extractor "github.com/marekm4/color-extractor" + "github.com/gohugoio/hugo/common/paths" "github.com/disintegration/gift" @@ -64,6 +66,9 @@ type imageResource struct { metaInitErr error meta *imageMeta + dominantColorInit sync.Once + dominantColors []string + baseResource } @@ -135,6 +140,24 @@ func (i *imageResource) getExif() *exif.ExifInfo { 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. func (i *imageResource) Clone() resource.Resource { gr := i.baseResource.Clone().(baseResource) diff --git a/resources/image_test.go b/resources/image_test.go index 153a4e8c4..111ce66db 100644 --- a/resources/image_test.go +++ b/resources/image_test.go @@ -84,6 +84,10 @@ func TestImageTransformBasic(t *testing.T) { 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.ResourceType(), qt.Equals, "image") assertWidthHeight(image, 900, 562) diff --git a/resources/images/color.go b/resources/images/color.go index 057a9fb71..0eedecb89 100644 --- a/resources/images/color.go +++ b/resources/images/color.go @@ -45,6 +45,14 @@ func ReplaceColorInPalette(c color.Color, p color.Palette) { 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) { s = strings.TrimPrefix(s, "#") diff --git a/resources/images/color_test.go b/resources/images/color_test.go index 52871e691..c3860a82c 100644 --- a/resources/images/color_test.go +++ b/resources/images/color_test.go @@ -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) { c := qt.New(t) diff --git a/resources/images/image_resource.go b/resources/images/image_resource.go index e0fec15a0..4e66b010c 100644 --- a/resources/images/image_resource.go +++ b/resources/images/image_resource.go @@ -48,6 +48,10 @@ type ImageResourceOps interface { // Exif returns an ExifInfo object containing Image metadata. Exif() *exif.ExifInfo + // Colors returns a slice of the most dominant colors in an image + // using a simple histogram method. + Colors() ([]string, error) + // Internal DecodeImage() (image.Image, error) } diff --git a/resources/transform.go b/resources/transform.go index 7d81f9b21..14120fda0 100644 --- a/resources/transform.go +++ b/resources/transform.go @@ -213,6 +213,10 @@ func (r *resourceAdapter) Exif() *exif.ExifInfo { return r.getImageOps().Exif() } +func (r *resourceAdapter) Colors() ([]string, error) { + return r.getImageOps().Colors() +} + func (r *resourceAdapter) Key() string { r.init(false, false) return r.target.(resource.Identifier).Key() |