1
0
mirror of https://github.com/coalaura/ffwebp.git synced 2025-09-08 05:49:54 +00:00
This commit is contained in:
2025-08-11 21:17:25 +02:00
parent 06bf222988
commit 738a9f42aa
19 changed files with 442 additions and 12 deletions

View File

@@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/coalaura/ffwebp/internal/codec" "github.com/coalaura/ffwebp/internal/codec"
"github.com/coalaura/ffwebp/internal/effects"
"github.com/coalaura/ffwebp/internal/logx" "github.com/coalaura/ffwebp/internal/logx"
) )
@@ -20,6 +21,10 @@ func banner() {
sort.Strings(names) sort.Strings(names)
if effects.HasEffects() {
names = append(names, "effects")
}
build := strings.Join(names, ",") build := strings.Join(names, ",")
logx.Printf("ffwebp version %s\n", Version) logx.Printf("ffwebp version %s\n", Version)

View File

@@ -9,6 +9,7 @@ import (
_ "github.com/coalaura/ffwebp/internal/builtins" _ "github.com/coalaura/ffwebp/internal/builtins"
"github.com/coalaura/ffwebp/internal/codec" "github.com/coalaura/ffwebp/internal/codec"
"github.com/coalaura/ffwebp/internal/effects"
"github.com/coalaura/ffwebp/internal/logx" "github.com/coalaura/ffwebp/internal/logx"
"github.com/coalaura/ffwebp/internal/opts" "github.com/coalaura/ffwebp/internal/opts"
"github.com/nfnt/resize" "github.com/nfnt/resize"
@@ -18,7 +19,7 @@ import (
var Version = "dev" var Version = "dev"
func main() { func main() {
flags := codec.Flags([]cli.Flag{ flags := []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "input", Name: "input",
Aliases: []string{"i"}, Aliases: []string{"i"},
@@ -64,7 +65,10 @@ func main() {
return nil return nil
}, },
}, },
}) }
flags = codec.Flags(flags)
flags = effects.Flags(flags)
app := &cli.Command{ app := &cli.Command{
Name: "ffwebp", Name: "ffwebp",
@@ -88,6 +92,7 @@ func run(_ context.Context, cmd *cli.Command) error {
banner() banner()
var ( var (
n int
input string input string
output string output string
@@ -167,12 +172,21 @@ func run(_ context.Context, cmd *cli.Command) error {
t1 := time.Now() 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) err = oCodec.Encode(writer, img, common)
if err != nil { if err != nil {
return err 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 return nil
} }

3
go.mod
View File

@@ -5,6 +5,7 @@ go 1.24.5
require github.com/urfave/cli/v3 v3.3.8 require github.com/urfave/cli/v3 v3.3.8
require ( require (
github.com/anthonynsimon/bild v0.14.0
github.com/ftrvxmtrx/tga v0.0.0-20150524081124-bd8e8d5be13a github.com/ftrvxmtrx/tga v0.0.0-20150524081124-bd8e8d5be13a
github.com/gen2brain/avif v0.4.4 github.com/gen2brain/avif v0.4.4
github.com/gen2brain/jpegxl v0.4.5 github.com/gen2brain/jpegxl v0.4.5
@@ -16,7 +17,7 @@ require (
github.com/sergeymakinen/go-ico v1.0.0-beta.0 github.com/sergeymakinen/go-ico v1.0.0-beta.0
github.com/spakin/netpbm v1.3.2 github.com/spakin/netpbm v1.3.2
github.com/xyproto/xbm v1.0.0 github.com/xyproto/xbm v1.0.0
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 golang.org/x/image v0.18.0
) )
require ( require (

7
go.sum
View File

@@ -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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= 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/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 h1:R5/A2+yhyy4V2c626bdIVfS1UNwE3efhiEBuKXU6u3A=
github.com/xyproto/xbm v1.0.0/go.mod h1:m2xrjsNmxuzBrx6gs4rSgxgpJWXS01j9KxYgjxhcnoc= 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.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 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/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View 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"
)

View File

@@ -42,7 +42,7 @@ func (impl) Flags(flags []cli.Flag) []cli.Flag {
return append(flags, return append(flags,
&cli.IntFlag{ &cli.IntFlag{
Name: "avif.quality-alpha", Name: "avif.quality-alpha",
Usage: "AVIF: alpha channel quality in range [0-100]", Usage: "AVIF: alpha channel quality (0-100)",
Value: 60, Value: 60,
Destination: &qualityA, Destination: &qualityA,
Validator: func(v int) error { Validator: func(v int) error {
@@ -55,7 +55,7 @@ func (impl) Flags(flags []cli.Flag) []cli.Flag {
}, },
&cli.IntFlag{ &cli.IntFlag{
Name: "avif.speed", 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, Value: 6,
Destination: &speed, Destination: &speed,
Validator: func(v int) error { Validator: func(v int) error {

View File

@@ -41,7 +41,7 @@ func (impl) Flags(flags []cli.Flag) []cli.Flag {
Destination: &numColors, Destination: &numColors,
Validator: func(value int) error { Validator: func(value int) error {
if value < 1 || value > 256 { if value < 1 || value > 256 {
return fmt.Errorf("invalid number of colors: %d", value) return fmt.Errorf("invalid gif.colors: %d", value)
} }
return nil return nil

View File

@@ -43,7 +43,7 @@ func (impl) Flags(flags []cli.Flag) []cli.Flag {
Destination: &compression, Destination: &compression,
Validator: func(value int) error { Validator: func(value int) error {
if value < 0 || value > 3 { if value < 0 || value > 3 {
return fmt.Errorf("invalid compression level: %d", value) return fmt.Errorf("invalid png.compression: %d", value)
} }
return nil return nil

View File

@@ -46,7 +46,7 @@ func (impl) Flags(flags []cli.Flag) []cli.Flag {
Destination: &compression, Destination: &compression,
Validator: func(value int) error { Validator: func(value int) error {
if value < 0 || value > 4 { if value < 0 || value > 4 {
return fmt.Errorf("invalid compression: %d", value) return fmt.Errorf("invalid tiff.compression: %d", value)
} }
return nil return nil

View 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
}

View 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
}

View 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
View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}