Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding image masking feature via resources/images/filter.go and mask.go #13106

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions resources/images/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ func (*Filters) Overlay(src ImageSource, x, y any) gift.Filter {
}
}

// Mask creates a filter that applies a mask image to the source image.
func (*Filters) Mask(mask ImageSource) gift.Filter {
return filter{
Options: newFilterOpts(mask.Key()),
Filter: maskFilter{mask: mask},
}
}

// 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 {
Expand Down
63 changes: 63 additions & 0 deletions resources/images/mask.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package images

import (
"fmt"
"image"
"image/color"
"image/draw"

"github.com/disintegration/gift"
)

// maskFilter applies a mask image to a base image.
type maskFilter struct {
mask ImageSource
}

// Draw applies the mask to the base image.
func (f maskFilter) Draw(dst draw.Image, baseImage image.Image, options *gift.Options) {
maskImage, err := f.mask.DecodeImage()
if err != nil {
panic(fmt.Sprintf("failed to decode image: %s", err))
}

// Ensure the mask is the same size as the base image
baseBounds := baseImage.Bounds()
maskBounds := maskImage.Bounds()

// Resize mask to match base image size if necessary
if maskBounds.Dx() != baseBounds.Dx() || maskBounds.Dy() != baseBounds.Dy() {
g := gift.New(gift.Resize(baseBounds.Dx(), baseBounds.Dy(), gift.LanczosResampling))
resizedMask := image.NewRGBA(g.Bounds(maskImage.Bounds()))
g.Draw(resizedMask, maskImage)
maskImage = resizedMask
}

// Use gift to convert the resized mask to grayscale
g := gift.New(gift.Grayscale())
grayscaleMask := image.NewGray(g.Bounds(maskImage.Bounds()))
g.Draw(grayscaleMask, maskImage)

// Convert grayscale mask to alpha mask
alphaMask := image.NewAlpha(baseBounds)
for y := baseBounds.Min.Y; y < baseBounds.Max.Y; y++ {
for x := baseBounds.Min.X; x < baseBounds.Max.X; x++ {
grayValue := grayscaleMask.GrayAt(x, y).Y
alphaMask.SetAlpha(x, y, color.Alpha{A: grayValue})
}
}

// Create an RGBA output image
outputImage := image.NewRGBA(baseBounds)

// Apply the mask using draw.DrawMask
draw.DrawMask(outputImage, baseBounds, baseImage, image.Point{}, alphaMask, image.Point{}, draw.Over)

// Copy the result to the destination
gift.New().Draw(dst, outputImage)
}

// Bounds returns the bounds of the resulting image.
func (f maskFilter) Bounds(imgBounds image.Rectangle) image.Rectangle {
return image.Rect(0, 0, imgBounds.Dx(), imgBounds.Dy())
}
Loading