Add Effort option

This commit is contained in:
Milan Nikolic
2026-06-23 05:34:18 +02:00
parent 09630243fb
commit b7c422fe33
4 changed files with 82 additions and 7 deletions
+3
View File
@@ -25,6 +25,8 @@ type Options struct {
Archive string
// JPEG image quality
Quality int
// Encoder speed/effort, format-specific: webp method 0-6, avif speed 0-10, jxl effort 1-10; -1 uses the format default
Effort int
// Image width
Width int
// Image height
@@ -125,6 +127,7 @@ func NewOptions() Options {
o.Format = "jpeg"
o.Archive = "zip"
o.Quality = 75
o.Effort = -1
o.Filter = 2
return o
+15 -3
View File
@@ -398,11 +398,23 @@ func (c *Converter) imageEncode(img image.Image, w io.Writer) error {
opts.DCTMethod = jpegli.DefaultDCTMethod
err = jpegli.Encode(w, img, opts)
case "webp":
err = webp.Encode(w, img, webp.Options{Quality: c.Opts.Quality, Method: webp.DefaultMethod})
method := webp.DefaultMethod
if c.Opts.Effort >= 0 {
method = min(max(c.Opts.Effort, 0), 6)
}
err = webp.Encode(w, img, webp.Options{Quality: c.Opts.Quality, Method: method})
case "avif":
err = avif.Encode(w, img, avif.Options{Quality: c.Opts.Quality, Speed: avif.DefaultSpeed})
speed := avif.DefaultSpeed
if c.Opts.Effort >= 0 {
speed = min(max(c.Opts.Effort, 0), 10)
}
err = avif.Encode(w, img, avif.Options{Quality: c.Opts.Quality, Speed: speed})
case "jxl":
err = jpegxl.Encode(w, img, jpegxl.Options{Quality: c.Opts.Quality, Effort: jpegxl.DefaultEffort})
effort := jpegxl.DefaultEffort
if c.Opts.Effort >= 0 {
effort = min(max(c.Opts.Effort, 1), 10)
}
err = jpegxl.Encode(w, img, jpegxl.Options{Quality: c.Opts.Quality, Effort: effort})
case "bmp":
opts := &gobmp.EncoderOptions{}
opts.SupportTransparency(false)
+58
View File
@@ -140,6 +140,12 @@ func options() cbconvert.Options {
opts.Fit = iup.GetHandle("Fit").GetAttribute("VALUE") == "ON"
opts.Filter = iup.GetHandle("Filter").GetInt("VALUE") - 1
opts.Quality = iup.GetHandle("Quality").GetInt("VALUE")
switch opts.Format {
case "webp", "avif", "jxl":
opts.Effort = iup.GetHandle("Effort").GetInt("VALUE")
default:
opts.Effort = -1
}
opts.Grayscale = iup.GetHandle("Grayscale").GetAttribute("VALUE") == "ON"
opts.Brightness = iup.GetHandle("Brightness").GetInt("VALUE")
opts.Contrast = iup.GetHandle("Contrast").GetInt("VALUE")
@@ -200,6 +206,12 @@ func setActive() {
iup.GetHandle("VboxQuality").SetAttribute("ACTIVE", "NO")
}
if (opts.Format == "webp" || opts.Format == "avif" || opts.Format == "jxl") && !opts.NoConvert {
iup.GetHandle("VboxEffort").SetAttribute("ACTIVE", "YES")
} else {
iup.GetHandle("VboxEffort").SetAttribute("ACTIVE", "NO")
}
if opts.Width != 0 && opts.Height != 0 && !opts.NoConvert {
iup.GetHandle("Fit").SetAttribute("ACTIVE", "YES")
} else {
@@ -207,6 +219,32 @@ func setActive() {
}
}
func setEffort(format string) {
val := iup.GetHandle("Effort")
var name string
switch format {
case "webp":
val.SetAttributes("MIN=0, MAX=6, SHOWTICKS=7, VALUE=4")
val.SetAttribute("TIP", "WEBP method, higher is better/slower (0-6, default 4)")
name = "Method"
case "avif":
val.SetAttributes("MIN=0, MAX=10, SHOWTICKS=11, VALUE=10")
val.SetAttribute("TIP", "AVIF speed, higher is faster/worse (0-10, default 10)")
name = "Speed"
case "jxl":
val.SetAttributes("MIN=1, MAX=10, SHOWTICKS=10, VALUE=7")
val.SetAttribute("TIP", "JXL effort, higher is better/slower (1-10, default 7)")
name = "Effort"
default:
return
}
val.SetAttribute("EFFORTNAME", name)
iup.GetHandle("LabelEffort").SetAttribute("TITLE", fmt.Sprintf("%s: %d", name, val.GetInt("VALUE")))
}
func layout() iup.Ihandle {
return iup.Vbox(
iup.Hbox(
@@ -409,6 +447,7 @@ func tabs() iup.Ihandle {
"7": "JXL",
}).SetHandle("Format").
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
setEffort(strings.ToLower(ih.GetAttribute("VALUESTRING")))
setActive()
previewPost()
@@ -494,6 +533,25 @@ func tabs() iup.Ihandle {
return iup.DEFAULT
})),
).SetHandle("VboxQuality"),
iup.Vbox(
iup.Label("").SetHandle("LabelEffort"),
iup.Val("").SetAttributes(`MIN=0, MAX=10, VALUE=0, SHOWTICKS=11`).SetHandle("Effort").
SetAttribute("TIP", "Encoder speed/effort (WEBP, AVIF, JXL)").
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
iup.GetHandle("LabelEffort").SetAttribute("TITLE", fmt.Sprintf("%s: %d", ih.GetAttribute("EFFORTNAME"), ih.GetInt("VALUE")))
ih.SetAttribute("MYVALUE", ih.GetInt("VALUE"))
return iup.DEFAULT
})).
SetCallback("KILLFOCUS_CB", iup.KillFocusFunc(func(ih iup.Ihandle) int {
if ih.GetAttribute("MYVALUE") != "" {
previewPost()
}
ih.SetAttribute("MYVALUE", "")
return iup.DEFAULT
})),
).SetHandle("VboxEffort"),
iup.Vbox(
iup.Toggle(" Grayscale").SetHandle("Grayscale").
SetAttributes(`TIP="Convert images to grayscale (monochromatic)"`).
+6 -4
View File
@@ -167,6 +167,7 @@ func parseFlags() (cbconvert.Options, []string) {
convert.StringVar(&opts.Format, "format", "jpeg", "Image format, valid values are jpeg, png, tiff, bmp, webp, avif, jxl")
convert.StringVar(&opts.Archive, "archive", "zip", "Archive format, valid values are zip, tar")
convert.IntVar(&opts.Quality, "quality", 75, "Image quality")
convert.IntVar(&opts.Effort, "effort", -1, "Encoder speed/effort, format-specific (webp method 0-6, avif speed 0-10, jxl effort 1-10), -1 uses the format default")
convert.IntVar(&opts.Filter, "filter", 2, "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos")
convert.BoolVar(&opts.NoCover, "no-cover", false, "Do not convert the cover image")
convert.BoolVar(&opts.NoRGB, "no-rgb", false, "Do not convert images that have RGB colorspace")
@@ -188,6 +189,7 @@ func parseFlags() (cbconvert.Options, []string) {
cover.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
cover.StringVar(&opts.Format, "format", "jpeg", "Image format, valid values are jpeg, png, tiff, bmp, webp, avif")
cover.IntVar(&opts.Quality, "quality", 75, "Image quality")
cover.IntVar(&opts.Effort, "effort", -1, "Encoder speed/effort, format-specific (webp method 0-6, avif speed 0-10, jxl effort 1-10), -1 uses the format default")
cover.IntVar(&opts.Filter, "filter", 2, "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos")
cover.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
cover.IntVar(&opts.Size, "size", 0, "Process only files larger than size (in MB)")
@@ -218,7 +220,7 @@ func parseFlags() (cbconvert.Options, []string) {
fmt.Fprintf(os.Stderr, "Usage: %s <command> [<flags>] [file1 dir1 ... fileOrDirN]\n\n", filepath.Base(os.Args[0]))
fmt.Fprintf(os.Stderr, "\nCommands:\n")
fmt.Fprintf(os.Stderr, "\n convert\n \tConvert archive or document\n\n")
order := []string{"width", "height", "fit", "format", "archive", "quality", "filter", "no-cover", "no-rgb",
order := []string{"width", "height", "fit", "format", "archive", "quality", "effort", "filter", "no-cover", "no-rgb",
"no-nonimage", "no-convert", "grayscale", "rotate", "brightness", "contrast", "suffix", "outdir", "size", "recursive", "quiet"}
for _, name := range order {
f := convert.Lookup(name)
@@ -226,7 +228,7 @@ func parseFlags() (cbconvert.Options, []string) {
fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
}
fmt.Fprintf(os.Stderr, "\n cover\n \tExtract cover\n\n")
order = []string{"width", "height", "fit", "format", "quality", "filter", "outdir", "size", "recursive", "quiet"}
order = []string{"width", "height", "fit", "format", "quality", "effort", "filter", "outdir", "size", "recursive", "quiet"}
for _, name := range order {
f := cover.Lookup(name)
fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
@@ -297,7 +299,7 @@ func parseFlags() (cbconvert.Options, []string) {
return opts, args
}
// piped checks if we have a piped stdin.
// piped checks if we have piped stdin.
func piped() bool {
f, err := os.Stdin.Stat()
if err != nil {
@@ -311,7 +313,7 @@ func piped() bool {
return true
}
// lines returns slice of lines from reader.
// lines returns slice of lines from the reader.
func lines(r io.Reader) []string {
data := make([]string, 0)
scanner := bufio.NewScanner(r)