|
@ -31,6 +31,11 @@ type ReadSeekCloser interface {
|
||||||
io.Closer
|
io.Closer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadSeekCloserProvider provides a ReadSeekCloser.
|
||||||
|
type ReadSeekCloserProvider interface {
|
||||||
|
ReadSeekCloser() (ReadSeekCloser, error)
|
||||||
|
}
|
||||||
|
|
||||||
// ReadSeekerNoOpCloser implements ReadSeekCloser by doing nothing in Close.
|
// ReadSeekerNoOpCloser implements ReadSeekCloser by doing nothing in Close.
|
||||||
// TODO(bep) rename this and similar to ReadSeekerNopCloser, naming used in stdlib, which kind of makes sense.
|
// TODO(bep) rename this and similar to ReadSeekerNopCloser, naming used in stdlib, which kind of makes sense.
|
||||||
type ReadSeekerNoOpCloser struct {
|
type ReadSeekerNoOpCloser struct {
|
||||||
|
|
|
@ -42,6 +42,8 @@ The above will overlay `$logo` in the upper left corner of `$img` (at position `
|
||||||
|
|
||||||
### Text
|
### Text
|
||||||
|
|
||||||
|
{{< new-in "0.90.0" >}}
|
||||||
|
|
||||||
Using the `Text` filter, you can add text to an image.
|
Using the `Text` filter, you can add text to an image.
|
||||||
|
|
||||||
{{% funcsig %}}
|
{{% funcsig %}}
|
||||||
|
@ -50,7 +52,7 @@ images.Text TEXT DICT)
|
||||||
|
|
||||||
The following example will add the text `Hugo rocks!` to the image with the specified color, size and position.
|
The following example will add the text `Hugo rocks!` to the image with the specified color, size and position.
|
||||||
|
|
||||||
```
|
```go-html-template
|
||||||
{{ $img := resources.Get "/images/background.png"}}
|
{{ $img := resources.Get "/images/background.png"}}
|
||||||
{{ $img = $img.Filter (images.Text "Hugo rocks!" (dict
|
{{ $img = $img.Filter (images.Text "Hugo rocks!" (dict
|
||||||
"color" "#ffffff"
|
"color" "#ffffff"
|
||||||
|
@ -61,6 +63,18 @@ 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.Get "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
|
||||||
|
"font" $font
|
||||||
|
))}}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### Brightness
|
### Brightness
|
||||||
|
|
||||||
{{% funcsig %}}
|
{{% funcsig %}}
|
||||||
|
|
|
@ -597,6 +597,8 @@ func TestImageOperationsGolden(t *testing.T) {
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
c.Parallel()
|
c.Parallel()
|
||||||
|
|
||||||
|
// Note, if you're enabling this on a MacOS M1 (ARM) you need to run the test with GOARCH=amd64.
|
||||||
|
// GOARCH=amd64 go test -timeout 30s -run "^TestImageOperationsGolden$" ./resources -v
|
||||||
devMode := false
|
devMode := false
|
||||||
|
|
||||||
testImages := []string{"sunset.jpg", "gohugoio8.png", "gohugoio24.png"}
|
testImages := []string{"sunset.jpg", "gohugoio8.png", "gohugoio24.png"}
|
||||||
|
|
|
@ -15,7 +15,11 @@
|
||||||
package images
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
"github.com/gohugoio/hugo/common/maps"
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
|
"github.com/gohugoio/hugo/resources/resource"
|
||||||
|
|
||||||
"github.com/disintegration/gift"
|
"github.com/disintegration/gift"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
|
@ -61,6 +65,21 @@ func (*Filters) Text(text string, options ...interface{}) gift.Filter {
|
||||||
tf.y = cast.ToInt(v)
|
tf.y = cast.ToInt(v)
|
||||||
case "linespacing":
|
case "linespacing":
|
||||||
tf.linespacing = cast.ToInt(v)
|
tf.linespacing = cast.ToInt(v)
|
||||||
|
case "font":
|
||||||
|
fontSource, ok1 := v.(hugio.ReadSeekCloserProvider)
|
||||||
|
identifier, ok2 := v.(resource.Identifier)
|
||||||
|
|
||||||
|
if !(ok1 && ok2) {
|
||||||
|
panic(fmt.Sprintf("invalid text font source: %T", v))
|
||||||
|
}
|
||||||
|
|
||||||
|
tf.fontSource = fontSource
|
||||||
|
|
||||||
|
// The input value isn't hashable and will not make a stable key.
|
||||||
|
// Replace it with a string in the map used as basis for the
|
||||||
|
// hash string.
|
||||||
|
opt["font"] = identifier.Key()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,11 @@ package images
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
"image/draw"
|
"image/draw"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/disintegration/gift"
|
"github.com/disintegration/gift"
|
||||||
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
|
|
||||||
"golang.org/x/image/font"
|
"golang.org/x/image/font"
|
||||||
"golang.org/x/image/font/gofont/goregular"
|
"golang.org/x/image/font/gofont/goregular"
|
||||||
|
@ -33,6 +35,7 @@ type textFilter struct {
|
||||||
x, y int
|
x, y int
|
||||||
size float64
|
size float64
|
||||||
linespacing int
|
linespacing int
|
||||||
|
fontSource hugio.ReadSeekCloserProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -43,6 +46,17 @@ func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options)
|
||||||
|
|
||||||
// Load and parse font
|
// Load and parse font
|
||||||
ttf := goregular.TTF
|
ttf := goregular.TTF
|
||||||
|
if f.fontSource != nil {
|
||||||
|
rs, err := f.fontSource.ReadSeekCloser()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer rs.Close()
|
||||||
|
ttf, err = io.ReadAll(rs)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
otf, err := opentype.Parse(ttf)
|
otf, err := opentype.Parse(ttf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
|
@ -155,11 +155,7 @@ type OpenReadSeekCloser func() (hugio.ReadSeekCloser, error)
|
||||||
// ReadSeekCloserResource is a Resource that supports loading its content.
|
// ReadSeekCloserResource is a Resource that supports loading its content.
|
||||||
type ReadSeekCloserResource interface {
|
type ReadSeekCloserResource interface {
|
||||||
MediaType() media.Type
|
MediaType() media.Type
|
||||||
ReadSeekCloserProvider
|
hugio.ReadSeekCloserProvider
|
||||||
}
|
|
||||||
|
|
||||||
type ReadSeekCloserProvider interface {
|
|
||||||
ReadSeekCloser() (hugio.ReadSeekCloser, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LengthProvider is a Resource that provides a length
|
// LengthProvider is a Resource that provides a length
|
||||||
|
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |