summaryrefslogtreecommitdiffstats
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
parentdb14238ba323279b28e7ad4cfff5aa10909ccd18 (diff)
resources/images: Create padding image filter
Closes #11599
-rw-r--r--docs/content/en/functions/images/index.md28
-rw-r--r--resources/images/color.go8
-rw-r--r--resources/images/filters.go64
-rw-r--r--resources/images/padding.go50
4 files changed, 146 insertions, 4 deletions
diff --git a/docs/content/en/functions/images/index.md b/docs/content/en/functions/images/index.md
index a71c2b61c..f886caa72 100644
--- a/docs/content/en/functions/images/index.md
+++ b/docs/content/en/functions/images/index.md
@@ -104,7 +104,6 @@ The following example will add the text `Hugo rocks!` to the image with the spec
You can load a custom font if needed. Load the font as a Hugo `Resource` and set it as an option:
```go-html-template
-
{{ $font := resources.GetRemote "https://github.com/google/fonts/raw/main/apache/roboto/static/Roboto-Black.ttf" }}
{{ $img := resources.Get "/images/background.png" }}
{{ $img = $img.Filter (images.Text "Hugo rocks!" (dict
@@ -112,6 +111,33 @@ You can load a custom font if needed. Load the font as a Hugo `Resource` and set
))}}
```
+## Padding
+
+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.
+
+[hexadecimal color]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color
+[shorthand property]: https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties#edges_of_a_box
+
+{{% funcsig %}}
+images.Padding V1 [V2] [V3] [V4] [COLOR]
+{{% /funcsig %}}
+
+This example resizes the image to 300px wide, converts it to the WebP format, adds 20px vertical padding and 50px horizontal padding, then sets the canvas color to dark green with 33% opacity.
+
+```go-html-template
+{{ $img := resources.Get "images/a.jpg" }}
+{{ $filters := slice
+ (images.Process "resize 300x webp")
+ (images.Padding 20 50 "#0705")
+}}
+{{ $img = $img.Filter $filters }}
+```
+
+To add a 2px gray border to an image:
+
+```go-html-template
+{{ $img = $img.Filter (images.Padding 2 "#777") }}
+```
## Brightness
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)
+}