filters.go (7489B)
1 // Copyright 2019 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 // Package images provides template functions for manipulating images.
15 package images
16
17 import (
18 "fmt"
19
20 "github.com/gohugoio/hugo/common/hugio"
21 "github.com/gohugoio/hugo/common/maps"
22 "github.com/gohugoio/hugo/resources/resource"
23
24 "github.com/disintegration/gift"
25 "github.com/spf13/cast"
26 )
27
28 // Increment for re-generation of images using these filters.
29 const filterAPIVersion = 0
30
31 type Filters struct {
32 }
33
34 // Overlay creates a filter that overlays src at position x y.
35 func (*Filters) Overlay(src ImageSource, x, y any) gift.Filter {
36 return filter{
37 Options: newFilterOpts(src.Key(), x, y),
38 Filter: overlayFilter{src: src, x: cast.ToInt(x), y: cast.ToInt(y)},
39 }
40 }
41
42 // Text creates a filter that draws text with the given options.
43 func (*Filters) Text(text string, options ...any) gift.Filter {
44 tf := textFilter{
45 text: text,
46 color: "#ffffff",
47 size: 20,
48 x: 10,
49 y: 10,
50 linespacing: 2,
51 }
52
53 var opt maps.Params
54 if len(options) > 0 {
55 opt = maps.MustToParamsAndPrepare(options[0])
56 for option, v := range opt {
57 switch option {
58 case "color":
59 tf.color = cast.ToString(v)
60 case "size":
61 tf.size = cast.ToFloat64(v)
62 case "x":
63 tf.x = cast.ToInt(v)
64 case "y":
65 tf.y = cast.ToInt(v)
66 case "linespacing":
67 tf.linespacing = cast.ToInt(v)
68 case "font":
69 if err, ok := v.(error); ok {
70 panic(fmt.Sprintf("invalid font source: %s", err))
71 }
72 fontSource, ok1 := v.(hugio.ReadSeekCloserProvider)
73 identifier, ok2 := v.(resource.Identifier)
74
75 if !(ok1 && ok2) {
76 panic(fmt.Sprintf("invalid text font source: %T", v))
77 }
78
79 tf.fontSource = fontSource
80
81 // The input value isn't hashable and will not make a stable key.
82 // Replace it with a string in the map used as basis for the
83 // hash string.
84 opt["font"] = identifier.Key()
85
86 }
87 }
88 }
89
90 return filter{
91 Options: newFilterOpts(text, opt),
92 Filter: tf,
93 }
94 }
95
96 // Brightness creates a filter that changes the brightness of an image.
97 // The percentage parameter must be in range (-100, 100).
98 func (*Filters) Brightness(percentage any) gift.Filter {
99 return filter{
100 Options: newFilterOpts(percentage),
101 Filter: gift.Brightness(cast.ToFloat32(percentage)),
102 }
103 }
104
105 // ColorBalance creates a filter that changes the color balance of an image.
106 // The percentage parameters for each color channel (red, green, blue) must be in range (-100, 500).
107 func (*Filters) ColorBalance(percentageRed, percentageGreen, percentageBlue any) gift.Filter {
108 return filter{
109 Options: newFilterOpts(percentageRed, percentageGreen, percentageBlue),
110 Filter: gift.ColorBalance(cast.ToFloat32(percentageRed), cast.ToFloat32(percentageGreen), cast.ToFloat32(percentageBlue)),
111 }
112 }
113
114 // Colorize creates a filter that produces a colorized version of an image.
115 // The hue parameter is the angle on the color wheel, typically in range (0, 360).
116 // The saturation parameter must be in range (0, 100).
117 // The percentage parameter specifies the strength of the effect, it must be in range (0, 100).
118 func (*Filters) Colorize(hue, saturation, percentage any) gift.Filter {
119 return filter{
120 Options: newFilterOpts(hue, saturation, percentage),
121 Filter: gift.Colorize(cast.ToFloat32(hue), cast.ToFloat32(saturation), cast.ToFloat32(percentage)),
122 }
123 }
124
125 // Contrast creates a filter that changes the contrast of an image.
126 // The percentage parameter must be in range (-100, 100).
127 func (*Filters) Contrast(percentage any) gift.Filter {
128 return filter{
129 Options: newFilterOpts(percentage),
130 Filter: gift.Contrast(cast.ToFloat32(percentage)),
131 }
132 }
133
134 // Gamma creates a filter that performs a gamma correction on an image.
135 // The gamma parameter must be positive. Gamma = 1 gives the original image.
136 // Gamma less than 1 darkens the image and gamma greater than 1 lightens it.
137 func (*Filters) Gamma(gamma any) gift.Filter {
138 return filter{
139 Options: newFilterOpts(gamma),
140 Filter: gift.Gamma(cast.ToFloat32(gamma)),
141 }
142 }
143
144 // GaussianBlur creates a filter that applies a gaussian blur to an image.
145 func (*Filters) GaussianBlur(sigma any) gift.Filter {
146 return filter{
147 Options: newFilterOpts(sigma),
148 Filter: gift.GaussianBlur(cast.ToFloat32(sigma)),
149 }
150 }
151
152 // Grayscale creates a filter that produces a grayscale version of an image.
153 func (*Filters) Grayscale() gift.Filter {
154 return filter{
155 Filter: gift.Grayscale(),
156 }
157 }
158
159 // Hue creates a filter that rotates the hue of an image.
160 // The hue angle shift is typically in range -180 to 180.
161 func (*Filters) Hue(shift any) gift.Filter {
162 return filter{
163 Options: newFilterOpts(shift),
164 Filter: gift.Hue(cast.ToFloat32(shift)),
165 }
166 }
167
168 // Invert creates a filter that negates the colors of an image.
169 func (*Filters) Invert() gift.Filter {
170 return filter{
171 Filter: gift.Invert(),
172 }
173 }
174
175 // Pixelate creates a filter that applies a pixelation effect to an image.
176 func (*Filters) Pixelate(size any) gift.Filter {
177 return filter{
178 Options: newFilterOpts(size),
179 Filter: gift.Pixelate(cast.ToInt(size)),
180 }
181 }
182
183 // Saturation creates a filter that changes the saturation of an image.
184 func (*Filters) Saturation(percentage any) gift.Filter {
185 return filter{
186 Options: newFilterOpts(percentage),
187 Filter: gift.Saturation(cast.ToFloat32(percentage)),
188 }
189 }
190
191 // Sepia creates a filter that produces a sepia-toned version of an image.
192 func (*Filters) Sepia(percentage any) gift.Filter {
193 return filter{
194 Options: newFilterOpts(percentage),
195 Filter: gift.Sepia(cast.ToFloat32(percentage)),
196 }
197 }
198
199 // Sigmoid creates a filter that changes the contrast of an image using a sigmoidal function and returns the adjusted image.
200 // It's a non-linear contrast change useful for photo adjustments as it preserves highlight and shadow detail.
201 func (*Filters) Sigmoid(midpoint, factor any) gift.Filter {
202 return filter{
203 Options: newFilterOpts(midpoint, factor),
204 Filter: gift.Sigmoid(cast.ToFloat32(midpoint), cast.ToFloat32(factor)),
205 }
206 }
207
208 // UnsharpMask creates a filter that sharpens an image.
209 // The sigma parameter is used in a gaussian function and affects the radius of effect.
210 // Sigma must be positive. Sharpen radius roughly equals 3 * sigma.
211 // The amount parameter controls how much darker and how much lighter the edge borders become. Typically between 0.5 and 1.5.
212 // The threshold parameter controls the minimum brightness change that will be sharpened. Typically between 0 and 0.05.
213 func (*Filters) UnsharpMask(sigma, amount, threshold any) gift.Filter {
214 return filter{
215 Options: newFilterOpts(sigma, amount, threshold),
216 Filter: gift.UnsharpMask(cast.ToFloat32(sigma), cast.ToFloat32(amount), cast.ToFloat32(threshold)),
217 }
218 }
219
220 type filter struct {
221 Options filterOpts
222 gift.Filter
223 }
224
225 // For cache-busting.
226 type filterOpts struct {
227 Version int
228 Vals any
229 }
230
231 func newFilterOpts(vals ...any) filterOpts {
232 return filterOpts{
233 Version: filterAPIVersion,
234 Vals: vals,
235 }
236 }