summaryrefslogtreecommitdiffstats
path: root/resources
diff options
context:
space:
mode:
authorJoe Mooring <joe.mooring@veriphor.com>2023-10-27 20:19:39 -0700
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-10-29 10:16:37 +0100
commit3ed28e4bfe2d333fdf1ca2434b57cfc10c3d9791 (patch)
tree165df55f1f5090b98423be2d8c1d7e78ad7bee41 /resources
parentdb14238ba323279b28e7ad4cfff5aa10909ccd18 (diff)
resources/images: Create padding image filter
Closes #11599
Diffstat (limited to 'resources')
-rw-r--r--resources/images/color.go8
-rw-r--r--resources/images/filters.go64
-rw-r--r--resources/images/padding.go50
3 files changed, 119 insertions, 3 deletions
diff --git a/resources/images/color.go b/resources/images/color.go
index 0eedecb89..fe891fe88 100644
--- a/resources/images/color.go
+++ b/resources/images/color.go
@@ -56,13 +56,13 @@ func ColorToHexString(c color.Color) string {
func hexStringToColor(s string) (color.Color, error) {
s = strings.TrimPrefix(s, "#")
- if len(s) != 3 && len(s) != 6 {
+ if len(s) != 3 && len(s) != 4 && len(s) != 6 && len(s) != 8 {
return nil, fmt.Errorf("invalid color code: %q", s)
}
s = strings.ToLower(s)
- if len(s) == 3 {
+ if len(s) == 3 || len(s) == 4 {
var v string
for _, r := range s {
v += string(r) + string(r)
@@ -80,7 +80,9 @@ func hexStringToColor(s string) (color.Color, error) {
}
// Set Alfa to white.
- s += "ff"
+ if len(s) == 6 {
+ s += "ff"
+ }
b, err := hex.DecodeString(s)
if err != nil {
diff --git a/resources/images/filters.go b/resources/images/filters.go
index dca8ff0e8..2a1f41a03 100644
--- a/resources/images/filters.go
+++ b/resources/images/filters.go
@@ -16,6 +16,7 @@ package images
import (
"fmt"
+ "image/color"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/maps"
@@ -30,6 +31,7 @@ const filterAPIVersion = 0
type Filters struct{}
+// Process creates a filter that processes an image using the given specification.
func (*Filters) Process(spec any) gift.Filter {
return filter{
Options: newFilterOpts(spec),
@@ -110,6 +112,68 @@ func (*Filters) Text(text string, options ...any) gift.Filter {
}
}
+// Padding creates a filter that resizes the image canvas without resizing the
+// image. The last argument is the canvas color, expressed as an RGB or RGBA
+// hexadecimal color. The default value is `ffffffff` (opaque white). The
+// preceding arguments are the padding values, in pixels, using the CSS
+// shorthand property syntax. Negative padding values will crop the image. The
+// signature is images.Padding V1 [V2] [V3] [V4] [COLOR].
+func (*Filters) Padding(args ...any) gift.Filter {
+ if len(args) < 1 || len(args) > 5 {
+ panic("the padding filter requires between 1 and 5 arguments")
+ }
+
+ var top, right, bottom, left int
+ var ccolor color.Color = color.White // canvas color
+ var err error
+
+ _args := args // preserve original args for most stable hash
+
+ if vcs, ok := (args[len(args)-1]).(string); ok {
+ ccolor, err = hexStringToColor(vcs)
+ if err != nil {
+ panic("invalid canvas color: specify RGB or RGBA using hex notation")
+ }
+ args = args[:len(args)-1]
+ if len(args) == 0 {
+ panic("not enough arguments: provide one or more padding values using the CSS shorthand property syntax")
+ }
+ }
+
+ var vals []int
+ for _, v := range args {
+ vi := cast.ToInt(v)
+ if vi > 5000 {
+ panic("padding values must not exceed 5000 pixels")
+ }
+ vals = append(vals, vi)
+ }
+
+ switch len(args) {
+ case 1:
+ top, right, bottom, left = vals[0], vals[0], vals[0], vals[0]
+ case 2:
+ top, right, bottom, left = vals[0], vals[1], vals[0], vals[1]
+ case 3:
+ top, right, bottom, left = vals[0], vals[1], vals[2], vals[1]
+ case 4:
+ top, right, bottom, left = vals[0], vals[1], vals[2], vals[3]
+ default:
+ panic(fmt.Sprintf("too many padding values: received %d, expected maximum of 4", len(args)))
+ }
+
+ return filter{
+ Options: newFilterOpts(_args...),
+ Filter: paddingFilter{
+ top: top,
+ right: right,
+ bottom: bottom,
+ left: left,
+ ccolor: ccolor,
+ },
+ }
+}
+
// Brightness creates a filter that changes the brightness of an image.
// The percentage parameter must be in range (-100, 100).
func (*Filters) Brightness(percentage any) gift.Filter {
diff --git a/resources/images/padding.go b/resources/images/padding.go
new file mode 100644
index 000000000..153d0bd82
--- /dev/null
+++ b/resources/images/padding.go
@@ -0,0 +1,50 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package images
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+
+ "github.com/disintegration/gift"
+)
+
+var _ gift.Filter = (*paddingFilter)(nil)
+
+type paddingFilter struct {
+ top, right, bottom, left int
+ ccolor color.Color // canvas color
+}
+
+func (f paddingFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
+ w := src.Bounds().Dx() + f.left + f.right
+ h := src.Bounds().Dy() + f.top + f.bottom
+
+ if w < 1 {
+ panic("final image width will be less than 1 pixel: check padding values")
+ }
+ if h < 1 {
+ panic("final image height will be less than 1 pixel: check padding values")
+ }
+
+ i := image.NewRGBA(image.Rect(0, 0, w, h))
+ draw.Draw(i, i.Bounds(), image.NewUniform(f.ccolor), image.Point{}, draw.Src)
+ gift.New().Draw(dst, i)
+ gift.New().DrawAt(dst, src, image.Pt(f.left, f.top), gift.OverOperator)
+}
+
+func (f paddingFilter) Bounds(srcBounds image.Rectangle) image.Rectangle {
+ return image.Rect(0, 0, srcBounds.Dx()+f.left+f.right, srcBounds.Dy()+f.top+f.bottom)
+}