]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
resources/images: Create padding image filter
authorJoe Mooring <joe.mooring@veriphor.com>
Sat, 28 Oct 2023 03:19:39 +0000 (20:19 -0700)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sun, 29 Oct 2023 09:16:37 +0000 (10:16 +0100)
Closes #11599

docs/content/en/functions/images/index.md
resources/images/color.go
resources/images/filters.go
resources/images/padding.go [new file with mode: 0644]

index a71c2b61c1b102b4a4ba84393f0eadf9687cb80b..f886caa72ed7ed83a35c38afcc2225ef13491125 100644 (file)
@@ -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
 
index 0eedecb890fcad743c14162ebe3f8be794712bcf..fe891fe8832013dc1c0ebc917b8722f5708f586f 100644 (file)
@@ -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 {
index dca8ff0e8fc972b7610e6296cc13cd5877df7fc9..2a1f41a0343dd4f9b6017f3b34c83fcff793ac3b 100644 (file)
@@ -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 (file)
index 0000000..153d0bd
--- /dev/null
@@ -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)
+}