Skip to content

Commit

Permalink
Add images.Opacity filter
Browse files Browse the repository at this point in the history
Fixes #11471
  • Loading branch information
bep committed Sep 22, 2023
1 parent 11fcda9 commit f9b3c0f
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 14 deletions.
13 changes: 13 additions & 0 deletions docs/content/en/functions/images/index.md
Expand Up @@ -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.
Expand Down
26 changes: 14 additions & 12 deletions resources/image_test.go
Expand Up @@ -63,7 +63,7 @@ var eq = qt.CmpEquals(
}
return p1.Name() == p2.Name() && p1.Size() == p2.Size() && p1.IsDir() == p2.IsDir()
}),
//cmp.Comparer(func(p1, p2 *genericResource) bool { return p1 == p2 }),
// cmp.Comparer(func(p1, p2 *genericResource) bool { return p1 == p2 }),
cmp.Comparer(func(m1, m2 media.Type) bool {
return m1.Type == m2.Type
}),
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
})

}

})
}

Expand Down Expand Up @@ -613,15 +608,14 @@ func TestImageOperationsGoldenWebp(t *testing.T) {
dir2 := filepath.FromSlash("testdata/golden_webp")

assetGoldenDirs(c, dir1, dir2)

}

func TestImageOperationsGolden(t *testing.T) {
c := qt.New(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
Expand All @@ -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)
Expand All @@ -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).
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 10 additions & 2 deletions resources/images/filters.go
Expand Up @@ -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 {
Expand All @@ -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{
Expand Down
39 changes: 39 additions & 0 deletions resources/images/opacity.go
@@ -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())
}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit f9b3c0f

Please sign in to comment.