diff --git a/build.cmd b/build.cmd
index 24e4f66..a981f78 100644
--- a/build.cmd
+++ b/build.cmd
@@ -1,10 +1,6 @@
@echo off
-if not exist bin (
- mkdir bin
-)
-
echo Building...
-go build -o bin/ffwebp.exe
+go build -o %USERPROFILE%/.bin/ffwebp.exe
-echo Done
\ No newline at end of file
+echo Done
diff --git a/ffwebp_test.go b/ffwebp_test.go
index c0c7ea1..a09f013 100644
--- a/ffwebp_test.go
+++ b/ffwebp_test.go
@@ -4,9 +4,7 @@ import (
"bytes"
"image/png"
"log"
- "os/exec"
- "path/filepath"
- "strings"
+ "os"
"testing"
)
@@ -28,40 +26,43 @@ var (
)
func TestFFWebP(t *testing.T) {
- exe, err := filepath.Abs("bin/ffwebp.exe")
- if err != nil {
- log.Fatalf("Failed to get absolute path for ffwebp.exe: %v\n", err)
- }
+ opts.Silent = true
for _, file := range TestFiles {
log.Printf("Testing file: %s\n", file)
- cmd := exec.Command(exe, "-i", file, "-f", "png", "-s")
-
- // Capture the output (which is expected to be a PNG image)
- var stdout bytes.Buffer
-
- cmd.Stdout = &stdout
-
- // Run the command
- err := cmd.Run()
+ in, err := os.OpenFile(file, os.O_RDONLY, 0)
if err != nil {
- out := strings.TrimSpace(stdout.String())
-
- log.Println(" - FAILED")
- log.Fatalf("Test failed for file: %s (%s)\n", file, out)
+ log.Fatalf("Failed to read %s: %v", file, err)
}
- // Decode the captured stdout output as a PNG image
- img, err := png.Decode(&stdout)
+ defer in.Close()
+
+ img, err := ReadImage(in)
+ if err != nil {
+ log.Fatalf("Failed to decode %s: %v", file, err)
+ }
+
+ before := img.Bounds()
+
+ var result bytes.Buffer
+
+ err = WriteImage(&result, img, "png")
+ if err != nil {
+ log.Fatalf("Failed to encode png image: %v", err)
+ }
+
+ img, err = png.Decode(&result)
if err != nil {
log.Println(" - FAILED")
log.Fatalf("Failed to decode PNG image: %v\n", err)
}
- if img == nil {
+ after := img.Bounds()
+
+ if before.Max.X != after.Max.X || before.Max.Y != after.Max.Y {
log.Println(" - FAILED")
- log.Fatalf("No image data returned for file: %s\n", file)
+ log.Fatalf("Invalid image (%dx%d != %dx%d) for file: %s\n", before.Max.X, before.Max.Y, after.Max.X, after.Max.Y, file)
}
log.Println(" - PASSED")
diff --git a/flags.go b/flags.go
deleted file mode 100644
index bfbf545..0000000
--- a/flags.go
+++ /dev/null
@@ -1,187 +0,0 @@
-package main
-
-import (
- "os"
- "strconv"
- "strings"
-)
-
-type Argument struct {
- IsNil bool
- Name string
- Value string
-}
-
-type Arguments struct {
- Arguments map[string]Argument
-}
-
-var (
- arguments Arguments
-)
-
-// I don't like golang flags package
-func init() {
- arguments = Arguments{
- Arguments: make(map[string]Argument),
- }
-
- var (
- arg string
- val string
- index int
-
- current Argument
- )
-
- for i := 1; i < len(os.Args); i++ {
- arg = os.Args[i]
-
- if arg[0] == '-' && len(arg) > 1 {
- if arg[1] == '-' {
- index = strings.Index(arg[2:], "=")
-
- if index >= 0 {
- val = ""
-
- if index+1 < len(arg) {
- val = arg[2+index+1:]
- }
-
- arguments.Set(Argument{
- Name: arg[2 : 2+index],
- Value: val,
- })
- } else {
- arguments.Set(Argument{
- Name: arg[2:],
- })
- }
-
- current = Argument{}
- } else {
- current = Argument{
- Name: arg[1:],
- }
- }
- } else {
- current.Value = arg
-
- arguments.Set(current)
-
- current = Argument{}
- }
- }
-
- if current.Name != "" {
- arguments.Set(current)
- }
-}
-
-func (a *Arguments) Set(arg Argument) {
- a.Arguments[arg.Name] = arg
-}
-
-func (a *Arguments) Get(short, long string) Argument {
- arg, ok := a.Arguments[short]
-
- if !ok && long != short {
- arg, ok = a.Arguments[long]
- }
-
- if !ok {
- return Argument{
- IsNil: true,
- Name: long,
- }
- }
-
- return arg
-}
-
-func (a *Arguments) GetString(short, long string) string {
- return a.Get(short, long).String()
-}
-
-func (a *Arguments) GetBool(short, long string, def bool) bool {
- return a.Get(short, long).Bool(def)
-}
-
-func (a *Arguments) GetInt64(short, long string, def, min, max int64) int64 {
- return a.Get(short, long).Int64(def, min, max)
-}
-
-func (a *Arguments) GetUint64(short, long string, def, min, max uint64) uint64 {
- return a.Get(short, long).Uint64(def, min, max)
-}
-
-func (a *Arguments) GetFloat64(short, long string, def, min, max float64) float64 {
- return a.Get(short, long).Float64(def, min, max)
-}
-
-func (a Argument) String() string {
- return a.Value
-}
-
-func (a Argument) Bool(def bool) bool {
- if a.IsNil {
- return def
- }
-
- if a.Value == "false" || a.Value == "0" {
- return false
- }
-
- return true
-}
-
-func (a Argument) Int64(def, min, max int64) int64 {
- if a.IsNil {
- return def
- }
-
- i, err := strconv.ParseInt(a.Value, 10, 64)
- if err != nil {
- return def
- }
-
- return minmax(i, min, max)
-}
-
-func (a Argument) Uint64(def, min, max uint64) uint64 {
- if a.IsNil {
- return def
- }
-
- i, err := strconv.ParseUint(a.Value, 10, 64)
- if err != nil {
- return def
- }
-
- return minmax(i, min, max)
-}
-
-func (a Argument) Float64(def, min, max float64) float64 {
- if a.IsNil {
- return def
- }
-
- i, err := strconv.ParseFloat(a.Value, 64)
- if err != nil {
- return def
- }
-
- return minmax(i, min, max)
-}
-
-func minmax[T int64 | uint64 | float64](val, min, max T) T {
- if min != 0 && val < min {
- return min
- }
-
- if max != 0 && val > max {
- return max
- }
-
- return val
-}
diff --git a/format.go b/format.go
index 21fce30..654bb23 100644
--- a/format.go
+++ b/format.go
@@ -7,11 +7,10 @@ import (
"image/jpeg"
"image/png"
"io"
- "os"
"path/filepath"
"strings"
- "github.com/biessek/golang-ico"
+ ico "github.com/biessek/golang-ico"
"github.com/gen2brain/avif"
"github.com/gen2brain/heic"
"github.com/gen2brain/jpegxl"
@@ -22,35 +21,35 @@ import (
var (
OutputFormats = []string{
- "jpeg",
- "png",
- "webp",
- "gif",
- "bmp",
- "tiff",
"avif",
- "jxl",
+ "bmp",
+ "gif",
"ico",
+ "jpeg",
+ "jxl",
+ "png",
+ "tiff",
+ "webp",
}
InputFormats = []string{
- "jpeg",
- "png",
- "webp",
- "gif",
- "bmp",
- "tiff",
"avif",
- "jxl",
- "ico",
+ "bmp",
+ "gif",
"heic",
"heif",
+ "ico",
+ "jpeg",
+ "jxl",
+ "png",
+ "tiff",
+ "webp",
}
)
type Decoder func(io.Reader) (image.Image, error)
-func GetDecoderFromContent(in *os.File) (Decoder, error) {
+func GetDecoderFromContent(in io.ReadSeeker) (Decoder, error) {
buffer := make([]byte, 128)
_, err := in.Read(buffer)
@@ -139,31 +138,31 @@ func IsJpegXL(buffer []byte) bool {
return len(buffer) > 12 && string(buffer[:4]) == "JXL "
}
-func OutputFormatFromPath(path string) string {
+func GetFormatFromPath(path string) string {
ext := strings.ToLower(filepath.Ext(path))
switch ext {
case ".webp", ".riff":
return "webp"
- case ".jpg", ".jpeg":
+ case ".jpg", ".jpeg", ".jpe", ".jif", ".jfif":
return "jpeg"
case ".png":
return "png"
- case ".gif":
+ case ".gif", ".giff":
return "gif"
- case ".bmp":
+ case ".bmp", ".dib", ".rle":
return "bmp"
case ".tiff", ".tif":
return "tiff"
case ".avif", ".avifs":
return "avif"
- case ".jxl":
+ case ".jxl", ".jxls":
return "jxl"
case ".ico":
return "ico"
}
- return "webp"
+ return ""
}
func IsValidOutputFormat(format string) bool {
diff --git a/go.mod b/go.mod
index 821dd33..ee20998 100644
--- a/go.mod
+++ b/go.mod
@@ -1,9 +1,10 @@
module ffwebp
-go 1.23.1
+go 1.23.4
require (
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
+ github.com/coalaura/arguments v1.5.2
github.com/gen2brain/avif v0.3.2
github.com/gen2brain/heic v0.3.1
github.com/gen2brain/jpegxl v0.3.1
diff --git a/go.sum b/go.sum
index 1ab44a6..267fd29 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,11 @@
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM=
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670/go.mod h1:iRWAFbKXMMkVQyxZ1PfGlkBr1TjATx1zy2MRprV7A3Q=
+github.com/coalaura/arguments v1.5.0 h1:apdZXxINepy8qCHYKJYEnLHvV6VbYgsIMPbZCWZAPQY=
+github.com/coalaura/arguments v1.5.0/go.mod h1:F5cdI+Gn1qi5K6qqvAdxdTD2TXkny+gTKU0o6NN1MlU=
+github.com/coalaura/arguments v1.5.1 h1:Gc7uODI3WlcVxmQpxoUQF7Y2j3EMDpSlVqhmGMtLC8Q=
+github.com/coalaura/arguments v1.5.1/go.mod h1:F5cdI+Gn1qi5K6qqvAdxdTD2TXkny+gTKU0o6NN1MlU=
+github.com/coalaura/arguments v1.5.2 h1:hRLKo6XmAzCDOS/unCUVAIYl3WU/i6QX59nBh0T31cw=
+github.com/coalaura/arguments v1.5.2/go.mod h1:F5cdI+Gn1qi5K6qqvAdxdTD2TXkny+gTKU0o6NN1MlU=
github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA=
github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/gen2brain/avif v0.3.2 h1:XUR0CBl5n4ISFJE8/pc1RMEKt5KUVoW8InctN+M7+DQ=
diff --git a/help.go b/help.go
index 794c510..2a3ee7f 100644
--- a/help.go
+++ b/help.go
@@ -3,46 +3,63 @@ package main
import (
"fmt"
"os"
- "sort"
- "strings"
+
+ "github.com/coalaura/arguments"
)
func help() {
- if !arguments.GetBool("h", "help", false) {
+ if !opts.Help {
return
}
- info(" __ __ _")
- info(" / _|/ _| | |")
- info("| |_| |___ _____| |__ _ __")
- info("| _| _\\ \\ /\\ / / _ \\ '_ \\| '_ \\")
- info("| | | | \\ V V / __/ |_) | |_) |")
- info("|_| |_| \\_/\\_/ \\___|_.__/| .__/")
- info(" | |")
- info(" %s |_|", Version)
+ println(" __ __ _")
+ println(" / _|/ _| | |")
+ println("| |_| |___ _____| |__ _ __")
+ println("| _| _\\ \\ /\\ / / _ \\ '_ \\| '_ \\")
+ println("| | | | \\ V V / __/ |_) | |_) |")
+ println("|_| |_| \\_/\\_/ \\___|_.__/| .__/")
+ println(" | |")
+ fmt.Printf(" %s |_|\n", Version)
- info("\nffwebp -i [output] [options]")
+ println("\nffwebp [options] [output]\n")
- var max int
+ arguments.ShowHelp(true)
- for name := range options {
- if len(name) > max {
- max = len(name)
- }
- }
+ b := arguments.NewBuilder(true)
- var formatted []string
+ b.WriteRune('\n')
+ b.Mute()
+ b.WriteString(" - ")
+ b.Name()
+ b.WriteString("Input formats")
+ b.Mute()
+ b.WriteString(": ")
+ values(b, InputFormats)
- for name, help := range options {
- formatted = append(formatted, fmt.Sprintf(" - %-*s: %s", max, name, help))
- }
+ b.WriteRune('\n')
+ b.Mute()
+ b.WriteString(" - ")
+ b.Name()
+ b.WriteString("Output formats")
+ b.Mute()
+ b.WriteString(": ")
+ values(b, OutputFormats)
- sort.Strings(formatted)
-
- info(strings.Join(formatted, "\n"))
-
- info("\nInput formats: %s", strings.Join(InputFormats, ", "))
- info("Output formats: %s", strings.Join(OutputFormats, ", "))
+ println(b.String())
os.Exit(0)
}
+
+func values(b *arguments.Builder, v []string) {
+ for i, value := range v {
+ if i > 0 {
+ b.Mute()
+ b.WriteString(", ")
+ }
+
+ b.Value()
+ b.WriteString(value)
+ }
+
+ b.Reset()
+}
diff --git a/image.go b/image.go
index 664a656..b4a26c0 100644
--- a/image.go
+++ b/image.go
@@ -5,10 +5,9 @@ import (
"image"
"image/gif"
"image/jpeg"
- "image/png"
- "os"
+ "io"
- "github.com/biessek/golang-ico"
+ ico "github.com/biessek/golang-ico"
"github.com/gen2brain/avif"
"github.com/gen2brain/jpegxl"
"github.com/gen2brain/webp"
@@ -16,23 +15,7 @@ import (
"golang.org/x/image/tiff"
)
-var (
- options = map[string]string{
- "c / colors": "Number of colors (1-256) (gif)",
- "e / effort": "Encoder effort level (0-10) (jxl)",
- "f / format": "Output format (avif, bmp, gif, jpeg, jxl, png, tiff, webp)",
- "h / help": "Show this help page",
- "l / lossless": "Use lossless compression (webp)",
- "m / method": "Encoder method (0=fast, 6=slower-better) (webp)",
- "r / ratio": "YCbCr subsample-ratio (0=444, 1=422, 2=420, 3=440, 4=411, 5=410) (avif)",
- "s / silent": "Do not print any output",
- "q / quality": "Set quality (0-100) (avif, jpeg, jxl, webp)",
- "x / exact": "Preserve RGB values in transparent area (webp)",
- "z / compression": "Compression type (0=uncompressed, 1=deflate, 2=lzw, 3=ccittgroup3, 4=ccittgroup4) (tiff)",
- }
-)
-
-func ReadImage(input *os.File) (image.Image, error) {
+func ReadImage(input io.ReadSeeker) (image.Image, error) {
decoder, err := GetDecoderFromContent(input)
if err != nil {
return nil, err
@@ -41,7 +24,7 @@ func ReadImage(input *os.File) (image.Image, error) {
return decoder(input)
}
-func WriteImage(output *os.File, img image.Image, format string) error {
+func WriteImage(output io.Writer, img image.Image, format string) error {
switch format {
case "webp":
options := GetWebPOptions()
@@ -56,7 +39,11 @@ func WriteImage(output *os.File, img image.Image, format string) error {
return jpeg.Encode(output, img, options)
case "png":
- return png.Encode(output, img)
+ encoder := GetPNGOptions()
+
+ LogPNGOptions(encoder)
+
+ return encoder.Encode(output, img)
case "gif":
options := GetGifOptions()
diff --git a/log.go b/log.go
index e8d1109..0cf8d1f 100644
--- a/log.go
+++ b/log.go
@@ -14,11 +14,13 @@ func info(fm string, args ...interface{}) {
return
}
- fmt.Printf(fm+"\n", args...)
+ fmt.Printf(fm, args...)
+ fmt.Println()
}
-func fatalf(code int, fm string, args ...interface{}) {
- fmt.Printf("ERROR: "+fm+"\n", args...)
+func fatalf(fm string, args ...interface{}) {
+ fmt.Printf("ERROR: "+fm, args...)
+ fmt.Println()
- os.Exit(code)
+ os.Exit(1)
}
diff --git a/main.go b/main.go
index 481cdac..650fca1 100644
--- a/main.go
+++ b/main.go
@@ -5,77 +5,46 @@ import (
)
func main() {
- help()
+ parse()
- silent = arguments.GetBool("s", "silent", false)
+ info("Reading input image...")
- // Read input file
- input := arguments.GetString("i", "input")
-
- var in *os.File
-
- if input == "" {
- in = os.Stdin
- } else {
- var err error
-
- in, err = os.OpenFile(input, os.O_RDONLY, 0)
- if err != nil {
- fatalf(1, "Failed to open input file: %s", err)
- }
- }
-
- // Read image
- if in == os.Stdin {
- info("Decoding input from stdin...")
- } else {
- info("Decoding input image...")
- }
-
- img, err := ReadImage(in)
+ in, err := os.OpenFile(opts.Input, os.O_RDONLY, 0)
if err != nil {
- fatalf(4, "Failed to read image: %s", err)
+ fatalf("Failed to open input file: %s", err)
}
- // Read output format
- format := arguments.GetString("f", "format")
-
- // Read output file
- output := arguments.GetString("", "")
+ defer in.Close()
var out *os.File
- if output == "" {
- if format == "" {
- format = "webp"
- }
+ if opts.Output == "" {
+ opts.Silent = true
out = os.Stdout
- silent = true
} else {
- var err error
-
- if format == "" {
- format = OutputFormatFromPath(output)
- }
-
- out, err = os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
+ out, err = os.OpenFile(opts.Output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
- fatalf(2, "Failed to open output file: %s", err)
+ fatalf("Failed to open output file: %s", err)
}
+
+ defer out.Close()
}
- if !IsValidOutputFormat(format) {
- fatalf(3, "Invalid output format: %s", format)
+ info("Decoding input image...")
+
+ img, err := ReadImage(in)
+ if err != nil {
+ fatalf("Failed to read image: %s", err)
}
- info("Using output format: %s", format)
+ info("Using output format: %s", opts.Format)
// Write image
info("Encoding output image...")
- err = WriteImage(out, img, format)
+ err = WriteImage(out, img, opts.Format)
if err != nil {
- fatalf(5, "Failed to write image: %s", err)
+ fatalf("Failed to write image: %s", err)
}
}
diff --git a/options.go b/options.go
index 24860c9..22b1a4c 100644
--- a/options.go
+++ b/options.go
@@ -4,19 +4,160 @@ import (
"image"
"image/gif"
"image/jpeg"
+ "image/png"
+ "github.com/coalaura/arguments"
"github.com/gen2brain/avif"
"github.com/gen2brain/jpegxl"
"github.com/gen2brain/webp"
"golang.org/x/image/tiff"
)
+type Options struct {
+ Help bool
+ Input string
+ Output string
+
+ Silent bool
+ NumColors int
+ Effort int
+ Format string
+ Lossless bool
+ Method int
+ Ratio int
+ Quality int
+ Exact bool
+ Compression int
+ Level int
+ Speed int
+}
+
+var opts = Options{
+ Help: false,
+ Input: "",
+ Output: "",
+
+ Silent: false,
+ NumColors: 256,
+ Effort: 10,
+ Format: "",
+ Lossless: false,
+ Method: 6,
+ Ratio: 0,
+ Quality: 90,
+ Exact: false,
+ Compression: 2,
+ Level: 2,
+ Speed: 0,
+}
+
+func parse() {
+ // General options
+ arguments.Register("help", 'h', &opts.Help).WithHelp("Show this help message")
+ arguments.Register("silent", 's', &opts.Silent).WithHelp("Do not print any output")
+ arguments.Register("format", 'f', &opts.Format).WithHelp("Output format (avif, bmp, gif, jpeg, jxl, png, tiff, webp, ico)")
+
+ // Common image options
+ arguments.Register("quality", 'q', &opts.Quality).WithHelp("[avif|jpeg|jxl|webp] Quality level (1-100)")
+
+ // AVIF
+ arguments.Register("ratio", 'r', &opts.Ratio).WithHelp("[avif] YCbCr subsample-ratio (0=444, 1=422, 2=420, 3=440, 4=411, 5=410)")
+ arguments.Register("speed", 'p', &opts.Speed).WithHelp("[avif] Encoder speed level (0=fast, 10=slower-better)")
+
+ // GIF
+ arguments.Register("colors", 'c', &opts.NumColors).WithHelp("[gif] Number of colors to use (1-256)")
+
+ // JXL
+ arguments.Register("effort", 'e', &opts.Effort).WithHelp("[jxl] Encoder effort level (0=fast, 10=slower-better)")
+
+ // PNG
+ arguments.Register("level", 'g', &opts.Level).WithHelp("[png] Compression level (0=no-compression, 1=best-speed, 2=best-compression)")
+
+ // TIFF
+ arguments.Register("compression", 't', &opts.Compression).WithHelp("[tiff] Compression type (0=uncompressed, 1=deflate, 2=lzw, 3=ccittgroup3, 4=ccittgroup4)")
+
+ // WebP
+ arguments.Register("exact", 'x', &opts.Exact).WithHelp("[webp] Preserve RGB values in transparent area")
+ arguments.Register("lossless", 'l', &opts.Lossless).WithHelp("[webp] Use lossless compression")
+ arguments.Register("method", 'm', &opts.Method).WithHelp("[webp] Encoder method (0=fast, 6=slower-better)")
+
+ arguments.Parse()
+
+ help()
+
+ if len(arguments.Args) < 1 {
+ fatalf("Missing input file")
+ }
+
+ opts.Input = arguments.Args[0]
+
+ if len(arguments.Args) > 1 {
+ opts.Output = arguments.Args[1]
+ }
+
+ if opts.Format != "" && !IsValidOutputFormat(opts.Format) {
+ fatalf("Invalid output format: %s", opts.Format)
+ }
+
+ // Resolve format from output file
+ if opts.Format == "" && opts.Output != "" {
+ opts.Format = GetFormatFromPath(opts.Output)
+ }
+
+ // Otherwise resolve format from input file
+ if opts.Format == "" {
+ opts.Format = GetFormatFromPath(opts.Input)
+ }
+
+ // Or default to webp
+ if opts.Format == "" {
+ opts.Format = "webp"
+ } else if opts.Format == "jpg" {
+ opts.Format = "jpeg"
+ }
+
+ // NumColors must be between 1 and 256
+ if opts.NumColors < 1 || opts.NumColors > 256 {
+ opts.NumColors = 256
+ }
+
+ // Effort must be between 0 and 10
+ if opts.Effort < 0 || opts.Effort > 10 {
+ opts.Effort = 10
+ }
+
+ // Method must be between 0 and 6
+ if opts.Method < 0 || opts.Method > 6 {
+ opts.Method = 6
+ }
+
+ // Quality must be between 1 and 100
+ if opts.Quality < 1 || opts.Quality > 100 {
+ opts.Quality = 90
+ }
+
+ // Ratio must be between 0 and 5
+ if opts.Ratio < 0 || opts.Ratio > 5 {
+ opts.Ratio = 0
+ }
+
+ // Compression must be between 0 and 4
+ if opts.Compression < 0 || opts.Compression > 4 {
+ opts.Compression = 2
+ }
+
+ // Level must be between 0 and 2
+ if opts.Level < 0 || opts.Level > 2 {
+ opts.Level = 2
+ }
+}
+
func GetWebPOptions() webp.Options {
return webp.Options{
- Lossless: arguments.GetBool("l", "lossless", false),
- Quality: int(arguments.GetUint64("q", "quality", 100, 0, 100)),
- Method: int(arguments.GetUint64("m", "method", 4, 0, 6)),
- Exact: arguments.GetBool("x", "exact", false),
+ Lossless: opts.Lossless,
+ Quality: opts.Quality,
+ Method: opts.Method,
+ Exact: opts.Exact,
}
}
@@ -30,7 +171,7 @@ func LogWebPOptions(options webp.Options) {
func GetJpegOptions() *jpeg.Options {
return &jpeg.Options{
- Quality: int(arguments.GetUint64("q", "quality", 100, 0, 100)),
+ Quality: opts.Quality,
}
}
@@ -39,9 +180,20 @@ func LogJpegOptions(options *jpeg.Options) {
info(" - quality: %v", options.Quality)
}
+func GetPNGOptions() *png.Encoder {
+ return &png.Encoder{
+ CompressionLevel: GetPNGCompressionLevel(),
+ }
+}
+
+func LogPNGOptions(encoder *png.Encoder) {
+ info("Using output options:")
+ info(" - level: %s", PNGCompressionLevelToString(encoder.CompressionLevel))
+}
+
func GetGifOptions() *gif.Options {
return &gif.Options{
- NumColors: int(arguments.GetUint64("c", "colors", 256, 0, 256)),
+ NumColors: opts.NumColors,
}
}
@@ -63,9 +215,9 @@ func LogTiffOptions(options *tiff.Options) {
func GetAvifOptions() avif.Options {
return avif.Options{
- Quality: int(arguments.GetUint64("q", "quality", 100, 0, 100)),
- QualityAlpha: int(arguments.GetUint64("qa", "quality-alpha", 100, 0, 100)),
- Speed: int(arguments.GetUint64("s", "speed", 6, 0, 10)),
+ Quality: opts.Quality,
+ QualityAlpha: opts.Quality,
+ Speed: opts.Speed,
ChromaSubsampling: GetAvifYCbCrSubsampleRatio(),
}
}
@@ -80,8 +232,8 @@ func LogAvifOptions(options avif.Options) {
func GetJxlOptions() jpegxl.Options {
return jpegxl.Options{
- Quality: int(arguments.GetUint64("q", "quality", 100, 0, 100)),
- Effort: int(arguments.GetUint64("e", "effort", 7, 0, 10)),
+ Quality: opts.Quality,
+ Effort: opts.Effort,
}
}
@@ -92,9 +244,7 @@ func LogJxlOptions(options jpegxl.Options) {
}
func GetTiffCompressionType() tiff.CompressionType {
- compression := arguments.GetUint64("z", "compression", 1, 0, 4)
-
- switch compression {
+ switch opts.Compression {
case 0:
return tiff.Uncompressed
case 1:
@@ -128,9 +278,7 @@ func TiffCompressionTypeToString(compression tiff.CompressionType) string {
}
func GetAvifYCbCrSubsampleRatio() image.YCbCrSubsampleRatio {
- sampleRatio := arguments.GetUint64("r", "sample-ratio", 0, 0, 5)
-
- switch sampleRatio {
+ switch opts.Ratio {
case 0:
return image.YCbCrSubsampleRatio444
case 1:
@@ -147,3 +295,29 @@ func GetAvifYCbCrSubsampleRatio() image.YCbCrSubsampleRatio {
return image.YCbCrSubsampleRatio444
}
+
+func GetPNGCompressionLevel() png.CompressionLevel {
+ switch opts.Level {
+ case 0:
+ return png.NoCompression
+ case 1:
+ return png.BestSpeed
+ case 2:
+ return png.BestCompression
+ }
+
+ return png.BestCompression
+}
+
+func PNGCompressionLevelToString(level png.CompressionLevel) string {
+ switch level {
+ case png.NoCompression:
+ return "no-compression"
+ case png.BestSpeed:
+ return "best-speed"
+ case png.BestCompression:
+ return "best-compression"
+ default:
+ return "unknown"
+ }
+}
diff --git a/version.go b/version.go
index 15baba7..c3205e5 100644
--- a/version.go
+++ b/version.go
@@ -1,3 +1,3 @@
package main
-const Version = "development"
+const Version = " dev"