Add images.Opacity filter

Fixes #11471
This commit is contained in:
Bjørn Erik Pedersen 2023-09-21 22:32:32 +02:00
parent 11fcda971c
commit f9b3c0f486
8 changed files with 76 additions and 14 deletions

View file

@ -34,6 +34,19 @@ A shorter version of the above, if you only need to apply the filter once:
The above will overlay `$logo` in the upper left corner of `$img` (at position `x=50, y=50`).
## Opacity
{{% funcsig %}}
images.Opacity SRC OPACITY
{{% /funcsig %}}
Opacity creates a filter that changes the opacity of an image.
The OPACITY parameter must be in range (0, 1).
```go-html-template
{{ $img := $img.Filter (images.Opacity 0.5 )}}
```
## Text
Using the `Text` filter, you can add text to an image.

View file

@ -162,7 +162,6 @@ func TestImageTransformBasic(t *testing.T) {
croppedAgain, err := image.Crop("300x300 topRight")
c.Assert(err, qt.IsNil)
c.Assert(cropped, qt.Equals, croppedAgain)
}
func TestImageTransformFormat(t *testing.T) {
@ -267,7 +266,6 @@ func TestImageBugs(t *testing.T) {
c.Assert(resized, qt.Not(qt.IsNil))
c.Assert(resized.Width(), qt.Equals, 100)
c.Assert(resized.RelPermalink(), qt.Equals, "/a/_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c876768085288f41211f768147ba2647.jpg")
})
// Issue #6137
@ -278,7 +276,6 @@ func TestImageBugs(t *testing.T) {
c.Assert(err, qt.IsNil)
c.Assert(resized, qt.Not(qt.IsNil))
c.Assert(resized.Width(), qt.Equals, 200)
})
// Issue #7955
@ -307,9 +304,7 @@ func TestImageBugs(t *testing.T) {
c.Assert(resized.Width(), qt.Equals, test.targetWH)
c.Assert(resized.Height(), qt.Equals, test.targetWH)
})
}
})
}
@ -613,7 +608,6 @@ func TestImageOperationsGoldenWebp(t *testing.T) {
dir2 := filepath.FromSlash("testdata/golden_webp")
assetGoldenDirs(c, dir1, dir2)
}
func TestImageOperationsGolden(t *testing.T) {
@ -621,7 +615,7 @@ func TestImageOperationsGolden(t *testing.T) {
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
// GOARCH=amd64 go test -count 1 -timeout 30s -run "^TestImageOperationsGolden$" ./resources -v
// The above will print out a folder.
// Replace testdata/golden with resources/_gen/images in that folder.
devMode := false
@ -644,6 +638,10 @@ func TestImageOperationsGolden(t *testing.T) {
gopher, err = gopher.Resize("30x")
c.Assert(err, qt.IsNil)
f := &images.Filters{}
sunset := fetchImageForSpec(spec, c, "sunset.jpg")
// Test PNGs with alpha channel.
for _, img := range []string{"gopher-hero8.png", "gradient-circle.png"} {
orig := fetchImageForSpec(spec, c, img)
@ -653,7 +651,15 @@ func TestImageOperationsGolden(t *testing.T) {
rel := resized.RelPermalink()
c.Assert(rel, qt.Not(qt.Equals), "")
}
// Check the Opacity filter.
opacity30, err := orig.Filter(f.Opacity(30))
c.Assert(err, qt.IsNil)
overlay, err := sunset.Filter(f.Overlay(opacity30.(images.ImageSource), 20, 20))
rel := overlay.RelPermalink()
c.Assert(rel, qt.Not(qt.Equals), "")
}
// A simple Gif file (no animation).
@ -699,8 +705,6 @@ func TestImageOperationsGolden(t *testing.T) {
c.Assert(rel, qt.Not(qt.Equals), "")
}
f := &images.Filters{}
filters := []gift.Filter{
f.Grayscale(),
f.GaussianBlur(6),
@ -746,11 +750,9 @@ func TestImageOperationsGolden(t *testing.T) {
dir2 := filepath.FromSlash("testdata/golden")
assetGoldenDirs(c, dir1, dir2)
}
func assetGoldenDirs(c *qt.C, dir1, dir2 string) {
// The two dirs above should now be the same.
dirinfos1, err := os.ReadDir(dir1)
c.Assert(err, qt.IsNil)

View file

@ -28,8 +28,7 @@ import (
// Increment for re-generation of images using these filters.
const filterAPIVersion = 0
type Filters struct {
}
type Filters struct{}
// Overlay creates a filter that overlays src at position x y.
func (*Filters) Overlay(src ImageSource, x, y any) gift.Filter {
@ -39,6 +38,15 @@ func (*Filters) Overlay(src ImageSource, x, y any) gift.Filter {
}
}
// Opacity creates a filter that changes the opacity of an image.
// The opacity parameter must be in range (0, 1).
func (*Filters) Opacity(opacity any) gift.Filter {
return filter{
Options: newFilterOpts(opacity),
Filter: opacityFilter{opacity: cast.ToFloat32(opacity)},
}
}
// Text creates a filter that draws text with the given options.
func (*Filters) Text(text string, options ...any) gift.Filter {
tf := textFilter{

View file

@ -0,0 +1,39 @@
// 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 = (*opacityFilter)(nil)
type opacityFilter struct {
opacity float32
}
func (f opacityFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
// 0 is fully transparent and 255 is opaque.
alpha := uint8(f.opacity * 255)
mask := image.NewUniform(color.Alpha{alpha})
draw.DrawMask(dst, dst.Bounds(), src, image.Point{}, mask, image.Point{}, draw.Over)
}
func (f opacityFilter) Bounds(srcBounds image.Rectangle) image.Rectangle {
return image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB