]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
resources/images: Add $image.Colors
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 21 Sep 2022 14:24:54 +0000 (16:24 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 22 Sep 2022 13:57:35 +0000 (15:57 +0200)
Which returns the most dominant colors of an image using a simple histogram method.

Fixes #10307

docs/content/en/content-management/image-processing/index.md
go.mod
go.sum
resources/errorResource.go
resources/image.go
resources/image_test.go
resources/images/color.go
resources/images/color_test.go
resources/images/image_resource.go
resources/transform.go

index f2748f5db461e41ad651bb664432e66326821fc7..d99ea78463609925c4edf49c6b7a4a2be3da9cd4 100644 (file)
@@ -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.
diff --git a/go.mod b/go.mod
index bbced89575aeae7977941207ff855b301ed47136..564a8de2d08228498b39b0b755e767cfe40875af 100644 (file)
--- a/go.mod
+++ b/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/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
diff --git a/go.sum b/go.sum
index 68d94b4347e388dbf88fbcb4bcada36716df0bb1..ac594a8e15febd2c565553b65e8f9c82d53081ed 100644 (file)
--- a/go.sum
+++ b/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/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=
index 81375cc485a38cefad965c999863bb70ad87ee30..e9411c3db0f1aa8c5fc820631353196b0eef8412 100644 (file)
@@ -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)
 }
index 8551cc2ab48722834c5809347f82586e95985fb9..ea8a21156cd164b0e79c364455026190132b0a42 100644 (file)
@@ -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)
index 153a4e8c4529142e9645e88d885d1005d3a1a6ac..111ce66db2b4bae26a4c827788cc6c0f26441a9f 100644 (file)
@@ -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)
index 057a9fb71379c1ba362657dc9f09f0926892f760..0eedecb890fcad743c14162ebe3f8be794712bcf 100644 (file)
@@ -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, "#")
 
index 52871e6914613c4fe037f48657b24c5f1b74a9f5..c3860a82c0c543ce59d2494293dbe076e8bc1d25 100644 (file)
@@ -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)
 
index e0fec15a0133a678c97a96c0e8e57f326088f08b..4e66b010cbf981f8423e64cbc07db7d19186d735 100644 (file)
@@ -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)
 }
index 7d81f9b2131f086b1fdbfa116e92d0962ba98f85..14120fda0a1d6570645c5e0303156a5000a14b83 100644 (file)
@@ -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()