mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
Add Luminance to Color
To sort an image's colors from darkest to lightest, you can then do: ```handlebars {{ {{ $colorsByLuminance := sort $image.Colors "Luminance" }} ``` This uses the formula defined here: https://www.w3.org/TR/WCAG21/#dfn-relative-luminance Fixes #10450
This commit is contained in:
parent
74e9129568
commit
e197c7b29d
12 changed files with 204 additions and 39 deletions
|
@ -123,6 +123,20 @@ func InSlicEqualFold(arr []string, el string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToString converts the given value to a string.
|
||||||
|
// Note that this is a more strict version compared to cast.ToString,
|
||||||
|
// as it will not try to convert numeric values to strings,
|
||||||
|
// but only accept strings or fmt.Stringer.
|
||||||
|
func ToString(v any) (string, bool) {
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case string:
|
||||||
|
return vv, true
|
||||||
|
case fmt.Stringer:
|
||||||
|
return vv.String(), true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
type Tuple struct {
|
type Tuple struct {
|
||||||
First string
|
First string
|
||||||
Second string
|
Second string
|
||||||
|
|
|
@ -128,7 +128,7 @@ func (e *errorResource) Exif() *exif.ExifInfo {
|
||||||
panic(e.ResourceError)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *errorResource) Colors() ([]string, error) {
|
func (e *errorResource) Colors() ([]images.Color, error) {
|
||||||
panic(e.ResourceError)
|
panic(e.ResourceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ type imageResource struct {
|
||||||
meta *imageMeta
|
meta *imageMeta
|
||||||
|
|
||||||
dominantColorInit sync.Once
|
dominantColorInit sync.Once
|
||||||
dominantColors []string
|
dominantColors []images.Color
|
||||||
|
|
||||||
baseResource
|
baseResource
|
||||||
}
|
}
|
||||||
|
@ -143,7 +143,7 @@ func (i *imageResource) getExif() *exif.ExifInfo {
|
||||||
|
|
||||||
// Colors returns a slice of the most dominant colors in an image
|
// Colors returns a slice of the most dominant colors in an image
|
||||||
// using a simple histogram method.
|
// using a simple histogram method.
|
||||||
func (i *imageResource) Colors() ([]string, error) {
|
func (i *imageResource) Colors() ([]images.Color, error) {
|
||||||
var err error
|
var err error
|
||||||
i.dominantColorInit.Do(func() {
|
i.dominantColorInit.Do(func() {
|
||||||
var img image.Image
|
var img image.Image
|
||||||
|
@ -153,7 +153,7 @@ func (i *imageResource) Colors() ([]string, error) {
|
||||||
}
|
}
|
||||||
colors := color_extractor.ExtractColors(img)
|
colors := color_extractor.ExtractColors(img)
|
||||||
for _, c := range colors {
|
for _, c := range colors {
|
||||||
i.dominantColors = append(i.dominantColors, images.ColorToHexString(c))
|
i.dominantColors = append(i.dominantColors, images.ColorGoToColor(c))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return i.dominantColors, nil
|
return i.dominantColors, nil
|
||||||
|
|
|
@ -85,9 +85,16 @@ func TestImageTransformBasic(t *testing.T) {
|
||||||
assertWidthHeight(c, img, w, h)
|
assertWidthHeight(c, img, w, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
colors, err := image.Colors()
|
gotColors, err := image.Colors()
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
c.Assert(colors, qt.DeepEquals, []string{"#2d2f33", "#a49e93", "#d39e59", "#a76936", "#737a84", "#7c838b"})
|
expectedColors := images.HexStringsToColors("#2d2f33", "#a49e93", "#d39e59", "#a76936", "#737a84", "#7c838b")
|
||||||
|
c.Assert(len(gotColors), qt.Equals, len(expectedColors))
|
||||||
|
for i := range gotColors {
|
||||||
|
c1, c2 := gotColors[i], expectedColors[i]
|
||||||
|
c.Assert(c1.ColorHex(), qt.Equals, c2.ColorHex())
|
||||||
|
c.Assert(c1.ColorGo(), qt.DeepEquals, c2.ColorGo())
|
||||||
|
c.Assert(c1.Luminance(), qt.Equals, c2.Luminance())
|
||||||
|
}
|
||||||
|
|
||||||
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")
|
||||||
|
@ -445,6 +452,24 @@ func TestImageExif(t *testing.T) {
|
||||||
getAndCheckExif(c, image)
|
getAndCheckExif(c, image)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageColorsLuminance(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
_, image := fetchSunset(c)
|
||||||
|
c.Assert(image, qt.Not(qt.IsNil))
|
||||||
|
colors, err := image.Colors()
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
c.Assert(len(colors), qt.Equals, 6)
|
||||||
|
var prevLuminance float64
|
||||||
|
for i, color := range colors {
|
||||||
|
luminance := color.Luminance()
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
c.Assert(luminance > 0, qt.IsTrue)
|
||||||
|
c.Assert(luminance, qt.Not(qt.Equals), prevLuminance, qt.Commentf("i=%d", i))
|
||||||
|
prevLuminance = luminance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkImageExif(b *testing.B) {
|
func BenchmarkImageExif(b *testing.B) {
|
||||||
getImages := func(c *qt.C, b *testing.B, fs afero.Fs) []images.ImageResource {
|
getImages := func(c *qt.C, b *testing.B, fs afero.Fs) []images.ImageResource {
|
||||||
spec := newTestResourceSpec(specDescriptor{fs: fs, c: c})
|
spec := newTestResourceSpec(specDescriptor{fs: fs, c: c})
|
||||||
|
|
|
@ -16,10 +16,76 @@ package images
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/hstrings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type colorGoProvider interface {
|
||||||
|
ColorGo() color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
type Color struct {
|
||||||
|
// The color.
|
||||||
|
color color.Color
|
||||||
|
|
||||||
|
// The color prefixed with a #.
|
||||||
|
hex string
|
||||||
|
|
||||||
|
// The relative luminance of the color.
|
||||||
|
luminance float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Luminance as defined by w3.org.
|
||||||
|
// See https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
|
||||||
|
func (c Color) Luminance() float64 {
|
||||||
|
return c.luminance
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorGo returns the color as a color.Color.
|
||||||
|
// For internal use only.
|
||||||
|
func (c Color) ColorGo() color.Color {
|
||||||
|
return c.color
|
||||||
|
}
|
||||||
|
|
||||||
|
// ColorHex returns the color as a hex string prefixed with a #.
|
||||||
|
func (c Color) ColorHex() string {
|
||||||
|
return c.hex
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the color as a hex string prefixed with a #.
|
||||||
|
func (c Color) String() string {
|
||||||
|
return c.hex
|
||||||
|
}
|
||||||
|
|
||||||
|
// For hashstructure. This struct is used in template func options
|
||||||
|
// that needs to be able to hash a Color.
|
||||||
|
// For internal use only.
|
||||||
|
func (c Color) Hash() (uint64, error) {
|
||||||
|
h := fnv.New64a()
|
||||||
|
h.Write([]byte(c.hex))
|
||||||
|
return h.Sum64(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) init() error {
|
||||||
|
c.hex = ColorGoToHexString(c.color)
|
||||||
|
r, g, b, _ := c.color.RGBA()
|
||||||
|
c.luminance = 0.2126*c.toSRGB(uint8(r)) + 0.7152*c.toSRGB(uint8(g)) + 0.0722*c.toSRGB(uint8(b))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Color) toSRGB(i uint8) float64 {
|
||||||
|
v := float64(i) / 255
|
||||||
|
if v <= 0.04045 {
|
||||||
|
return v / 12.92
|
||||||
|
} else {
|
||||||
|
return math.Pow((v+0.055)/1.055, 2.4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AddColorToPalette adds c as the first color in p if not already there.
|
// AddColorToPalette adds c as the first color in p if not already there.
|
||||||
// Note that it does no additional checks, so callers must make sure
|
// Note that it does no additional checks, so callers must make sure
|
||||||
// that the palette is valid for the relevant format.
|
// that the palette is valid for the relevant format.
|
||||||
|
@ -45,14 +111,60 @@ 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.
|
// ColorGoToHexString converts a color.Color to a hex string.
|
||||||
func ColorToHexString(c color.Color) string {
|
func ColorGoToHexString(c color.Color) string {
|
||||||
r, g, b, a := c.RGBA()
|
r, g, b, a := c.RGBA()
|
||||||
rgba := color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
|
rgba := color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
|
||||||
|
if rgba.A == 0xff {
|
||||||
return fmt.Sprintf("#%.2x%.2x%.2x", rgba.R, rgba.G, rgba.B)
|
return fmt.Sprintf("#%.2x%.2x%.2x", rgba.R, rgba.G, rgba.B)
|
||||||
}
|
}
|
||||||
|
return fmt.Sprintf("#%.2x%.2x%.2x%.2x", rgba.R, rgba.G, rgba.B, rgba.A)
|
||||||
|
}
|
||||||
|
|
||||||
func hexStringToColor(s string) (color.Color, error) {
|
// ColorGoToColor converts a color.Color to a Color.
|
||||||
|
func ColorGoToColor(c color.Color) Color {
|
||||||
|
cc := Color{color: c}
|
||||||
|
if err := cc.init(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return cc
|
||||||
|
}
|
||||||
|
|
||||||
|
func hexStringToColor(s string) Color {
|
||||||
|
c, err := hexStringToColorGo(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return ColorGoToColor(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HexStringsToColors converts a slice of hex strings to a slice of Colors.
|
||||||
|
func HexStringsToColors(s ...string) []Color {
|
||||||
|
var colors []Color
|
||||||
|
for _, v := range s {
|
||||||
|
colors = append(colors, hexStringToColor(v))
|
||||||
|
}
|
||||||
|
return colors
|
||||||
|
}
|
||||||
|
|
||||||
|
func toColorGo(v any) (color.Color, bool, error) {
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case colorGoProvider:
|
||||||
|
return vv.ColorGo(), true, nil
|
||||||
|
default:
|
||||||
|
s, ok := hstrings.ToString(v)
|
||||||
|
if !ok {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
c, err := hexStringToColorGo(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
return c, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hexStringToColorGo(s string) (color.Color, error) {
|
||||||
s = strings.TrimPrefix(s, "#")
|
s = strings.TrimPrefix(s, "#")
|
||||||
|
|
||||||
if len(s) != 3 && len(s) != 4 && len(s) != 6 && len(s) != 8 {
|
if len(s) != 3 && len(s) != 4 && len(s) != 6 && len(s) != 8 {
|
||||||
|
|
|
@ -46,7 +46,7 @@ func TestHexStringToColor(t *testing.T) {
|
||||||
c.Run(test.arg, func(c *qt.C) {
|
c.Run(test.arg, func(c *qt.C) {
|
||||||
c.Parallel()
|
c.Parallel()
|
||||||
|
|
||||||
result, err := hexStringToColor(test.arg)
|
result, err := hexStringToColorGo(test.arg)
|
||||||
|
|
||||||
if b, ok := test.expect.(bool); ok && !b {
|
if b, ok := test.expect.(bool); ok && !b {
|
||||||
c.Assert(err, qt.Not(qt.IsNil))
|
c.Assert(err, qt.Not(qt.IsNil))
|
||||||
|
@ -70,13 +70,18 @@ func TestColorToHexString(t *testing.T) {
|
||||||
{color.White, "#ffffff"},
|
{color.White, "#ffffff"},
|
||||||
{color.Black, "#000000"},
|
{color.Black, "#000000"},
|
||||||
{color.RGBA{R: 0x42, G: 0x87, B: 0xf5, A: 0xff}, "#4287f5"},
|
{color.RGBA{R: 0x42, G: 0x87, B: 0xf5, A: 0xff}, "#4287f5"},
|
||||||
|
|
||||||
|
// 50% opacity.
|
||||||
|
// Note that the .Colors (dominant colors) received from the Image resource
|
||||||
|
// will always have an alpha value of 0xff.
|
||||||
|
{color.RGBA{R: 0x42, G: 0x87, B: 0xf5, A: 0x80}, "#4287f580"},
|
||||||
} {
|
} {
|
||||||
|
|
||||||
test := test
|
test := test
|
||||||
c.Run(test.expect, func(c *qt.C) {
|
c.Run(test.expect, func(c *qt.C) {
|
||||||
c.Parallel()
|
c.Parallel()
|
||||||
|
|
||||||
result := ColorToHexString(test.arg)
|
result := ColorGoToHexString(test.arg)
|
||||||
|
|
||||||
c.Assert(result, qt.Equals, test.expect)
|
c.Assert(result, qt.Equals, test.expect)
|
||||||
})
|
})
|
||||||
|
@ -91,9 +96,9 @@ func TestAddColorToPalette(t *testing.T) {
|
||||||
|
|
||||||
c.Assert(AddColorToPalette(color.White, palette), qt.HasLen, 2)
|
c.Assert(AddColorToPalette(color.White, palette), qt.HasLen, 2)
|
||||||
|
|
||||||
blue1, _ := hexStringToColor("34c3eb")
|
blue1, _ := hexStringToColorGo("34c3eb")
|
||||||
blue2, _ := hexStringToColor("34c3eb")
|
blue2, _ := hexStringToColorGo("34c3eb")
|
||||||
white, _ := hexStringToColor("fff")
|
white, _ := hexStringToColorGo("fff")
|
||||||
|
|
||||||
c.Assert(AddColorToPalette(white, palette), qt.HasLen, 2)
|
c.Assert(AddColorToPalette(white, palette), qt.HasLen, 2)
|
||||||
c.Assert(AddColorToPalette(blue1, palette), qt.HasLen, 3)
|
c.Assert(AddColorToPalette(blue1, palette), qt.HasLen, 3)
|
||||||
|
@ -104,10 +109,18 @@ func TestReplaceColorInPalette(t *testing.T) {
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
|
|
||||||
palette := color.Palette{color.White, color.Black}
|
palette := color.Palette{color.White, color.Black}
|
||||||
offWhite, _ := hexStringToColor("fcfcfc")
|
offWhite, _ := hexStringToColorGo("fcfcfc")
|
||||||
|
|
||||||
ReplaceColorInPalette(offWhite, palette)
|
ReplaceColorInPalette(offWhite, palette)
|
||||||
|
|
||||||
c.Assert(palette, qt.HasLen, 2)
|
c.Assert(palette, qt.HasLen, 2)
|
||||||
c.Assert(palette[0], qt.Equals, offWhite)
|
c.Assert(palette[0], qt.Equals, offWhite)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestColorLuminance(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
c.Assert(hexStringToColor("#000000").Luminance(), qt.Equals, 0.0)
|
||||||
|
c.Assert(hexStringToColor("#768a9a").Luminance(), qt.Equals, 0.24361603589088263)
|
||||||
|
c.Assert(hexStringToColor("#d5bc9f").Luminance(), qt.Equals, 0.5261577672685374)
|
||||||
|
c.Assert(hexStringToColor("#ffffff").Luminance(), qt.Equals, 1.0)
|
||||||
|
}
|
||||||
|
|
|
@ -171,7 +171,7 @@ func DecodeConfig(in map[string]any) (*config.ConfigNamespace[ImagingConfig, Ima
|
||||||
return i, nil, err
|
return i, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
i.BgColor, err = hexStringToColor(i.Imaging.BgColor)
|
i.BgColor, err = hexStringToColorGo(i.Imaging.BgColor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return i, nil, err
|
return i, nil, err
|
||||||
}
|
}
|
||||||
|
@ -230,7 +230,7 @@ func DecodeImageConfig(action string, options []string, defaults *config.ConfigN
|
||||||
c.Hint = hint
|
c.Hint = hint
|
||||||
} else if part[0] == '#' {
|
} else if part[0] == '#' {
|
||||||
c.BgColorStr = part[1:]
|
c.BgColorStr = part[1:]
|
||||||
c.BgColor, err = hexStringToColor(c.BgColorStr)
|
c.BgColor, err = hexStringToColorGo(c.BgColorStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
@ -424,7 +424,7 @@ type ImagingConfigInternal struct {
|
||||||
|
|
||||||
func (i *ImagingConfigInternal) Compile(externalCfg *ImagingConfig) error {
|
func (i *ImagingConfigInternal) Compile(externalCfg *ImagingConfig) error {
|
||||||
var err error
|
var err error
|
||||||
i.BgColor, err = hexStringToColor(externalCfg.BgColor)
|
i.BgColor, err = hexStringToColorGo(externalCfg.BgColor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ func newImageConfig(action string, width, height, quality, rotate int, filter, a
|
||||||
c.qualitySetForImage = quality != 75
|
c.qualitySetForImage = quality != 75
|
||||||
c.Rotate = rotate
|
c.Rotate = rotate
|
||||||
c.BgColorStr = bgColor
|
c.BgColorStr = bgColor
|
||||||
c.BgColor, _ = hexStringToColor(bgColor)
|
c.BgColor, _ = hexStringToColorGo(bgColor)
|
||||||
|
|
||||||
if filter != "" {
|
if filter != "" {
|
||||||
filter = strings.ToLower(filter)
|
filter = strings.ToLower(filter)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2019 The Hugo Authors. All rights reserved.
|
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -65,7 +65,7 @@ func (*Filters) Opacity(opacity any) gift.Filter {
|
||||||
func (*Filters) Text(text string, options ...any) gift.Filter {
|
func (*Filters) Text(text string, options ...any) gift.Filter {
|
||||||
tf := textFilter{
|
tf := textFilter{
|
||||||
text: text,
|
text: text,
|
||||||
color: "#ffffff",
|
color: color.White,
|
||||||
size: 20,
|
size: 20,
|
||||||
x: 10,
|
x: 10,
|
||||||
y: 10,
|
y: 10,
|
||||||
|
@ -78,7 +78,9 @@ func (*Filters) Text(text string, options ...any) gift.Filter {
|
||||||
for option, v := range opt {
|
for option, v := range opt {
|
||||||
switch option {
|
switch option {
|
||||||
case "color":
|
case "color":
|
||||||
tf.color = cast.ToString(v)
|
if color, ok, _ := toColorGo(v); ok {
|
||||||
|
tf.color = color
|
||||||
|
}
|
||||||
case "size":
|
case "size":
|
||||||
tf.size = cast.ToFloat64(v)
|
tf.size = cast.ToFloat64(v)
|
||||||
case "x":
|
case "x":
|
||||||
|
@ -128,15 +130,14 @@ func (*Filters) Padding(args ...any) gift.Filter {
|
||||||
|
|
||||||
var top, right, bottom, left int
|
var top, right, bottom, left int
|
||||||
var ccolor color.Color = color.White // canvas color
|
var ccolor color.Color = color.White // canvas color
|
||||||
var err error
|
|
||||||
|
|
||||||
_args := args // preserve original args for most stable hash
|
_args := args // preserve original args for most stable hash
|
||||||
|
|
||||||
if vcs, ok := (args[len(args)-1]).(string); ok {
|
if vcs, ok, err := toColorGo(args[len(args)-1]); ok || err != nil {
|
||||||
ccolor, err = hexStringToColor(vcs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("invalid canvas color: specify RGB or RGBA using hex notation")
|
panic("invalid canvas color: specify RGB or RGBA using hex notation")
|
||||||
}
|
}
|
||||||
|
ccolor = vcs
|
||||||
args = args[:len(args)-1]
|
args = args[:len(args)-1]
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
panic("not enough arguments: provide one or more padding values using the CSS shorthand property syntax")
|
panic("not enough arguments: provide one or more padding values using the CSS shorthand property syntax")
|
||||||
|
@ -180,12 +181,11 @@ func (*Filters) Padding(args ...any) gift.Filter {
|
||||||
// Dither creates a filter that dithers an image.
|
// Dither creates a filter that dithers an image.
|
||||||
func (*Filters) Dither(options ...any) gift.Filter {
|
func (*Filters) Dither(options ...any) gift.Filter {
|
||||||
ditherOptions := struct {
|
ditherOptions := struct {
|
||||||
Colors []string
|
Colors []any
|
||||||
Method string
|
Method string
|
||||||
Serpentine bool
|
Serpentine bool
|
||||||
Strength float32
|
Strength float32
|
||||||
}{
|
}{
|
||||||
Colors: []string{"000000ff", "ffffffff"},
|
|
||||||
Method: "floydsteinberg",
|
Method: "floydsteinberg",
|
||||||
Serpentine: true,
|
Serpentine: true,
|
||||||
Strength: 1.0,
|
Strength: 1.0,
|
||||||
|
@ -198,14 +198,18 @@ func (*Filters) Dither(options ...any) gift.Filter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(ditherOptions.Colors) == 0 {
|
||||||
|
ditherOptions.Colors = []any{"000000ff", "ffffffff"}
|
||||||
|
}
|
||||||
|
|
||||||
if len(ditherOptions.Colors) < 2 {
|
if len(ditherOptions.Colors) < 2 {
|
||||||
panic("palette must have at least two colors")
|
panic("palette must have at least two colors")
|
||||||
}
|
}
|
||||||
|
|
||||||
var palette []color.Color
|
var palette []color.Color
|
||||||
for _, c := range ditherOptions.Colors {
|
for _, c := range ditherOptions.Colors {
|
||||||
cc, err := hexStringToColor(c)
|
cc, ok, err := toColorGo(c)
|
||||||
if err != nil {
|
if !ok || err != nil {
|
||||||
panic(fmt.Sprintf("%q is an invalid color: specify RGB or RGBA using hexadecimal notation", c))
|
panic(fmt.Sprintf("%q is an invalid color: specify RGB or RGBA using hexadecimal notation", c))
|
||||||
}
|
}
|
||||||
palette = append(palette, cc)
|
palette = append(palette, cc)
|
||||||
|
|
|
@ -63,7 +63,7 @@ type ImageResourceOps interface {
|
||||||
|
|
||||||
// Colors returns a slice of the most dominant colors in an image
|
// Colors returns a slice of the most dominant colors in an image
|
||||||
// using a simple histogram method.
|
// using a simple histogram method.
|
||||||
Colors() ([]string, error)
|
Colors() ([]Color, error)
|
||||||
|
|
||||||
// For internal use.
|
// For internal use.
|
||||||
DecodeImage() (image.Image, error)
|
DecodeImage() (image.Image, error)
|
||||||
|
|
|
@ -15,6 +15,7 @@ package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
|
"image/color"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -31,7 +32,8 @@ import (
|
||||||
var _ gift.Filter = (*textFilter)(nil)
|
var _ gift.Filter = (*textFilter)(nil)
|
||||||
|
|
||||||
type textFilter struct {
|
type textFilter struct {
|
||||||
text, color string
|
text string
|
||||||
|
color color.Color
|
||||||
x, y int
|
x, y int
|
||||||
size float64
|
size float64
|
||||||
linespacing int
|
linespacing int
|
||||||
|
@ -39,11 +41,6 @@ type textFilter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
|
func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
|
||||||
color, err := hexStringToColor(f.color)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load and parse font
|
// Load and parse font
|
||||||
ttf := goregular.TTF
|
ttf := goregular.TTF
|
||||||
if f.fontSource != nil {
|
if f.fontSource != nil {
|
||||||
|
@ -74,7 +71,7 @@ func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options)
|
||||||
|
|
||||||
d := font.Drawer{
|
d := font.Drawer{
|
||||||
Dst: dst,
|
Dst: dst,
|
||||||
Src: image.NewUniform(color),
|
Src: image.NewUniform(f.color),
|
||||||
Face: face,
|
Face: face,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -263,7 +263,7 @@ func (r *resourceAdapter) Exif() *exif.ExifInfo {
|
||||||
return r.getImageOps().Exif()
|
return r.getImageOps().Exif()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *resourceAdapter) Colors() ([]string, error) {
|
func (r *resourceAdapter) Colors() ([]images.Color, error) {
|
||||||
return r.getImageOps().Colors()
|
return r.getImageOps().Colors()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue