mirror of
https://github.com/coalaura/ffwebp.git
synced 2025-09-08 05:49:54 +00:00
effects
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/coalaura/ffwebp/internal/codec"
|
||||
"github.com/coalaura/ffwebp/internal/effects"
|
||||
"github.com/coalaura/ffwebp/internal/logx"
|
||||
)
|
||||
|
||||
@@ -20,6 +21,10 @@ func banner() {
|
||||
|
||||
sort.Strings(names)
|
||||
|
||||
if effects.HasEffects() {
|
||||
names = append(names, "effects")
|
||||
}
|
||||
|
||||
build := strings.Join(names, ",")
|
||||
|
||||
logx.Printf("ffwebp version %s\n", Version)
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
_ "github.com/coalaura/ffwebp/internal/builtins"
|
||||
"github.com/coalaura/ffwebp/internal/codec"
|
||||
"github.com/coalaura/ffwebp/internal/effects"
|
||||
"github.com/coalaura/ffwebp/internal/logx"
|
||||
"github.com/coalaura/ffwebp/internal/opts"
|
||||
"github.com/nfnt/resize"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
var Version = "dev"
|
||||
|
||||
func main() {
|
||||
flags := codec.Flags([]cli.Flag{
|
||||
flags := []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "input",
|
||||
Aliases: []string{"i"},
|
||||
@@ -64,7 +65,10 @@ func main() {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
flags = codec.Flags(flags)
|
||||
flags = effects.Flags(flags)
|
||||
|
||||
app := &cli.Command{
|
||||
Name: "ffwebp",
|
||||
@@ -88,6 +92,7 @@ func run(_ context.Context, cmd *cli.Command) error {
|
||||
banner()
|
||||
|
||||
var (
|
||||
n int
|
||||
input string
|
||||
output string
|
||||
|
||||
@@ -167,12 +172,21 @@ func run(_ context.Context, cmd *cli.Command) error {
|
||||
|
||||
t1 := time.Now()
|
||||
|
||||
img, n, err = effects.ApplyAll(img)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if n > 0 {
|
||||
logx.Printf("applied %d effect(s) in %s\n", n, time.Since(t1).Truncate(time.Millisecond))
|
||||
}
|
||||
|
||||
t2 := time.Now()
|
||||
|
||||
err = oCodec.Encode(writer, img, common)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logx.Printf("encoded %d KiB in %s\n", (writer.n+1023)/1024, time.Since(t1).Truncate(time.Millisecond))
|
||||
logx.Printf("encoded %d KiB in %s\n", (writer.n+1023)/1024, time.Since(t2).Truncate(time.Millisecond))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
3
go.mod
3
go.mod
@@ -5,6 +5,7 @@ go 1.24.5
|
||||
require github.com/urfave/cli/v3 v3.3.8
|
||||
|
||||
require (
|
||||
github.com/anthonynsimon/bild v0.14.0
|
||||
github.com/ftrvxmtrx/tga v0.0.0-20150524081124-bd8e8d5be13a
|
||||
github.com/gen2brain/avif v0.4.4
|
||||
github.com/gen2brain/jpegxl v0.4.5
|
||||
@@ -16,7 +17,7 @@ require (
|
||||
github.com/sergeymakinen/go-ico v1.0.0-beta.0
|
||||
github.com/spakin/netpbm v1.3.2
|
||||
github.com/xyproto/xbm v1.0.0
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8
|
||||
golang.org/x/image v0.18.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
7
go.sum
7
go.sum
@@ -1,3 +1,5 @@
|
||||
github.com/anthonynsimon/bild v0.14.0 h1:IFRkmKdNdqmexXHfEU7rPlAmdUZ8BDZEGtGHDnGWync=
|
||||
github.com/anthonynsimon/bild v0.14.0/go.mod h1:hcvEAyBjTW69qkKJTfpcDQ83sSZHxwOunsseDfeQhUs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
|
||||
@@ -37,9 +39,8 @@ github.com/urfave/cli/v3 v3.3.8 h1:BzolUExliMdet9NlJ/u4m5vHSotJ3PzEqSAZ1oPMa/E=
|
||||
github.com/urfave/cli/v3 v3.3.8/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
github.com/xyproto/xbm v1.0.0 h1:R5/A2+yhyy4V2c626bdIVfS1UNwE3efhiEBuKXU6u3A=
|
||||
github.com/xyproto/xbm v1.0.0/go.mod h1:m2xrjsNmxuzBrx6gs4rSgxgpJWXS01j9KxYgjxhcnoc=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
16
internal/builtins/effects.go
Normal file
16
internal/builtins/effects.go
Normal file
@@ -0,0 +1,16 @@
|
||||
//go:build effects || core || full
|
||||
// +build effects core full
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
_ "github.com/coalaura/ffwebp/internal/effects/blur"
|
||||
_ "github.com/coalaura/ffwebp/internal/effects/brightness"
|
||||
_ "github.com/coalaura/ffwebp/internal/effects/contrast"
|
||||
_ "github.com/coalaura/ffwebp/internal/effects/grayscale"
|
||||
_ "github.com/coalaura/ffwebp/internal/effects/hue"
|
||||
_ "github.com/coalaura/ffwebp/internal/effects/invert"
|
||||
_ "github.com/coalaura/ffwebp/internal/effects/saturation"
|
||||
_ "github.com/coalaura/ffwebp/internal/effects/sepia"
|
||||
_ "github.com/coalaura/ffwebp/internal/effects/sharpen"
|
||||
)
|
@@ -42,7 +42,7 @@ func (impl) Flags(flags []cli.Flag) []cli.Flag {
|
||||
return append(flags,
|
||||
&cli.IntFlag{
|
||||
Name: "avif.quality-alpha",
|
||||
Usage: "AVIF: alpha channel quality in range [0-100]",
|
||||
Usage: "AVIF: alpha channel quality (0-100)",
|
||||
Value: 60,
|
||||
Destination: &qualityA,
|
||||
Validator: func(v int) error {
|
||||
@@ -55,7 +55,7 @@ func (impl) Flags(flags []cli.Flag) []cli.Flag {
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Name: "avif.speed",
|
||||
Usage: "AVIF: encoding speed in range [0-10] (0=slowest/best)",
|
||||
Usage: "AVIF: encoding speed (0=slowest/best, 10=fastest/worst)",
|
||||
Value: 6,
|
||||
Destination: &speed,
|
||||
Validator: func(v int) error {
|
||||
|
@@ -41,7 +41,7 @@ func (impl) Flags(flags []cli.Flag) []cli.Flag {
|
||||
Destination: &numColors,
|
||||
Validator: func(value int) error {
|
||||
if value < 1 || value > 256 {
|
||||
return fmt.Errorf("invalid number of colors: %d", value)
|
||||
return fmt.Errorf("invalid gif.colors: %d", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@@ -43,7 +43,7 @@ func (impl) Flags(flags []cli.Flag) []cli.Flag {
|
||||
Destination: &compression,
|
||||
Validator: func(value int) error {
|
||||
if value < 0 || value > 3 {
|
||||
return fmt.Errorf("invalid compression level: %d", value)
|
||||
return fmt.Errorf("invalid png.compression: %d", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@@ -46,7 +46,7 @@ func (impl) Flags(flags []cli.Flag) []cli.Flag {
|
||||
Destination: &compression,
|
||||
Validator: func(value int) error {
|
||||
if value < 0 || value > 4 {
|
||||
return fmt.Errorf("invalid compression: %d", value)
|
||||
return fmt.Errorf("invalid tiff.compression: %d", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
38
internal/effects/blur/blur.go
Normal file
38
internal/effects/blur/blur.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package blur
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"strconv"
|
||||
|
||||
"github.com/anthonynsimon/bild/blur"
|
||||
"github.com/coalaura/ffwebp/internal/effects"
|
||||
"github.com/coalaura/ffwebp/internal/logx"
|
||||
)
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func init() {
|
||||
effects.Register(impl{})
|
||||
}
|
||||
|
||||
func (impl) String() string {
|
||||
return "blur"
|
||||
}
|
||||
|
||||
func (impl) Apply(img image.Image, args string) (image.Image, error) {
|
||||
var radius float64 = 3
|
||||
|
||||
if args != "" {
|
||||
f64, err := strconv.ParseFloat(args, 64)
|
||||
if err != nil || f64 <= 0 {
|
||||
return nil, fmt.Errorf("invalid blur radius: %s", args)
|
||||
}
|
||||
|
||||
radius = f64
|
||||
}
|
||||
|
||||
logx.Printf(" applying blur (radius=%.f)\n", radius)
|
||||
|
||||
return blur.Gaussian(img, radius), nil
|
||||
}
|
38
internal/effects/brightness/brightness.go
Normal file
38
internal/effects/brightness/brightness.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package brightness
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"strconv"
|
||||
|
||||
"github.com/anthonynsimon/bild/adjust"
|
||||
"github.com/coalaura/ffwebp/internal/effects"
|
||||
"github.com/coalaura/ffwebp/internal/logx"
|
||||
)
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func init() {
|
||||
effects.Register(impl{})
|
||||
}
|
||||
|
||||
func (impl) String() string {
|
||||
return "brightness"
|
||||
}
|
||||
|
||||
func (impl) Apply(img image.Image, args string) (image.Image, error) {
|
||||
var change float64 = 0.1
|
||||
|
||||
if args != "" {
|
||||
f64, err := strconv.ParseFloat(args, 64)
|
||||
if err != nil || f64 < -1 || f64 > 1 || f64 == 0 {
|
||||
return nil, fmt.Errorf("invalid brightness change: %s", args)
|
||||
}
|
||||
|
||||
change = f64
|
||||
}
|
||||
|
||||
logx.Printf(" applying brightness (change=%.f)\n", change)
|
||||
|
||||
return adjust.Brightness(img, change), nil
|
||||
}
|
38
internal/effects/contrast/contrast.go
Normal file
38
internal/effects/contrast/contrast.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package contrast
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"strconv"
|
||||
|
||||
"github.com/anthonynsimon/bild/adjust"
|
||||
"github.com/coalaura/ffwebp/internal/effects"
|
||||
"github.com/coalaura/ffwebp/internal/logx"
|
||||
)
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func init() {
|
||||
effects.Register(impl{})
|
||||
}
|
||||
|
||||
func (impl) String() string {
|
||||
return "contrast"
|
||||
}
|
||||
|
||||
func (impl) Apply(img image.Image, args string) (image.Image, error) {
|
||||
var change float64 = 0.1
|
||||
|
||||
if args != "" {
|
||||
f64, err := strconv.ParseFloat(args, 64)
|
||||
if err != nil || f64 < -1 || f64 > 1 || f64 == 0 {
|
||||
return nil, fmt.Errorf("invalid contrast change: %s", args)
|
||||
}
|
||||
|
||||
change = f64
|
||||
}
|
||||
|
||||
logx.Printf(" applying contrast (change=%.f)\n", change)
|
||||
|
||||
return adjust.Contrast(img, change), nil
|
||||
}
|
103
internal/effects/effects.go
Normal file
103
internal/effects/effects.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package effects
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
type Effect interface {
|
||||
String() string
|
||||
Apply(image.Image, string) (image.Image, error)
|
||||
}
|
||||
|
||||
type EffectConfig struct {
|
||||
Effect Effect
|
||||
Arguments string
|
||||
}
|
||||
|
||||
var (
|
||||
apply string
|
||||
names []string
|
||||
registry = make(map[string]Effect)
|
||||
)
|
||||
|
||||
func Register(e Effect) {
|
||||
name := e.String()
|
||||
|
||||
names = append(names, name)
|
||||
|
||||
registry[name] = e
|
||||
}
|
||||
|
||||
func HasEffects() bool {
|
||||
return len(registry) > 0
|
||||
}
|
||||
|
||||
func Flags(flags []cli.Flag) []cli.Flag {
|
||||
if len(registry) == 0 {
|
||||
return flags
|
||||
}
|
||||
|
||||
return append(flags,
|
||||
&cli.StringFlag{
|
||||
Name: "effects",
|
||||
Aliases: []string{"e"},
|
||||
Usage: fmt.Sprintf("list of effects to apply (%s)", strings.Join(names, ", ")),
|
||||
Value: "",
|
||||
Destination: &apply,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func ApplyAll(img image.Image) (image.Image, int, error) {
|
||||
if apply == "" {
|
||||
return img, 0, nil
|
||||
}
|
||||
|
||||
list, err := Parse()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
} else if len(list) == 0 {
|
||||
return img, 0, nil
|
||||
}
|
||||
|
||||
for _, e := range list {
|
||||
img, err = e.Effect.Apply(img, e.Arguments)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return img, len(list), nil
|
||||
}
|
||||
|
||||
func Parse() ([]EffectConfig, error) {
|
||||
var result []EffectConfig
|
||||
|
||||
for entry := range strings.SplitSeq(apply, ",") {
|
||||
var (
|
||||
name = entry
|
||||
arguments string
|
||||
)
|
||||
|
||||
if index := strings.Index(entry, ":"); index != -1 {
|
||||
name = entry[:index]
|
||||
arguments = entry[index+1:]
|
||||
}
|
||||
|
||||
effect, ok := registry[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid effect: %s", name)
|
||||
}
|
||||
|
||||
result = append(result, EffectConfig{
|
||||
Effect: effect,
|
||||
Arguments: arguments,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
25
internal/effects/grayscale/grayscale.go
Normal file
25
internal/effects/grayscale/grayscale.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package grayscale
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/anthonynsimon/bild/effect"
|
||||
"github.com/coalaura/ffwebp/internal/effects"
|
||||
"github.com/coalaura/ffwebp/internal/logx"
|
||||
)
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func init() {
|
||||
effects.Register(impl{})
|
||||
}
|
||||
|
||||
func (impl) String() string {
|
||||
return "grayscale"
|
||||
}
|
||||
|
||||
func (impl) Apply(img image.Image, _ string) (image.Image, error) {
|
||||
logx.Printf(" applying grayscale\n")
|
||||
|
||||
return effect.Grayscale(img), nil
|
||||
}
|
38
internal/effects/hue/hue.go
Normal file
38
internal/effects/hue/hue.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package hue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"strconv"
|
||||
|
||||
"github.com/anthonynsimon/bild/adjust"
|
||||
"github.com/coalaura/ffwebp/internal/effects"
|
||||
"github.com/coalaura/ffwebp/internal/logx"
|
||||
)
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func init() {
|
||||
effects.Register(impl{})
|
||||
}
|
||||
|
||||
func (impl) String() string {
|
||||
return "hue"
|
||||
}
|
||||
|
||||
func (impl) Apply(img image.Image, args string) (image.Image, error) {
|
||||
var change int64 = 10
|
||||
|
||||
if args != "" {
|
||||
i64, err := strconv.ParseInt(args, 10, 64)
|
||||
if err != nil || i64 < -360 || i64 > 360 || i64 == 0 {
|
||||
return nil, fmt.Errorf("invalid hue change: %s", args)
|
||||
}
|
||||
|
||||
change = i64
|
||||
}
|
||||
|
||||
logx.Printf(" applying hue (change=%d)\n", change)
|
||||
|
||||
return adjust.Hue(img, int(change)), nil
|
||||
}
|
25
internal/effects/invert/invert.go
Normal file
25
internal/effects/invert/invert.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package invert
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/anthonynsimon/bild/effect"
|
||||
"github.com/coalaura/ffwebp/internal/effects"
|
||||
"github.com/coalaura/ffwebp/internal/logx"
|
||||
)
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func init() {
|
||||
effects.Register(impl{})
|
||||
}
|
||||
|
||||
func (impl) String() string {
|
||||
return "invert"
|
||||
}
|
||||
|
||||
func (impl) Apply(img image.Image, _ string) (image.Image, error) {
|
||||
logx.Printf(" applying invert\n")
|
||||
|
||||
return effect.Invert(img), nil
|
||||
}
|
38
internal/effects/saturation/saturation.go
Normal file
38
internal/effects/saturation/saturation.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package saturation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"strconv"
|
||||
|
||||
"github.com/anthonynsimon/bild/adjust"
|
||||
"github.com/coalaura/ffwebp/internal/effects"
|
||||
"github.com/coalaura/ffwebp/internal/logx"
|
||||
)
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func init() {
|
||||
effects.Register(impl{})
|
||||
}
|
||||
|
||||
func (impl) String() string {
|
||||
return "saturation"
|
||||
}
|
||||
|
||||
func (impl) Apply(img image.Image, args string) (image.Image, error) {
|
||||
var change float64 = 0.1
|
||||
|
||||
if args != "" {
|
||||
f64, err := strconv.ParseFloat(args, 64)
|
||||
if err != nil || f64 < -1 || f64 > 1 || f64 == 0 {
|
||||
return nil, fmt.Errorf("invalid saturation change: %s", args)
|
||||
}
|
||||
|
||||
change = f64
|
||||
}
|
||||
|
||||
logx.Printf(" applying saturation (change=%.f)\n", change)
|
||||
|
||||
return adjust.Saturation(img, change), nil
|
||||
}
|
25
internal/effects/sepia/grayscale.go
Normal file
25
internal/effects/sepia/grayscale.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package sepia
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/anthonynsimon/bild/effect"
|
||||
"github.com/coalaura/ffwebp/internal/effects"
|
||||
"github.com/coalaura/ffwebp/internal/logx"
|
||||
)
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func init() {
|
||||
effects.Register(impl{})
|
||||
}
|
||||
|
||||
func (impl) String() string {
|
||||
return "sepia"
|
||||
}
|
||||
|
||||
func (impl) Apply(img image.Image, _ string) (image.Image, error) {
|
||||
logx.Printf(" applying sepia\n")
|
||||
|
||||
return effect.Sepia(img), nil
|
||||
}
|
25
internal/effects/sharpen/sharpen.go
Normal file
25
internal/effects/sharpen/sharpen.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package sharpen
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/anthonynsimon/bild/effect"
|
||||
"github.com/coalaura/ffwebp/internal/effects"
|
||||
"github.com/coalaura/ffwebp/internal/logx"
|
||||
)
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func init() {
|
||||
effects.Register(impl{})
|
||||
}
|
||||
|
||||
func (impl) String() string {
|
||||
return "sharpen"
|
||||
}
|
||||
|
||||
func (impl) Apply(img image.Image, _ string) (image.Image, error) {
|
||||
logx.Printf(" applying sharpen\n")
|
||||
|
||||
return effect.Sharpen(img), nil
|
||||
}
|
Reference in New Issue
Block a user