mirror of
https://github.com/gen2brain/cbconvert
synced 2026-06-30 09:11:54 +02:00
Add NoUpscale option
This commit is contained in:
@@ -70,6 +70,8 @@ Commands:
|
||||
Image height (default "0")
|
||||
--fit
|
||||
Best fit for required width and height (default "false")
|
||||
--no-upscale
|
||||
Do not upscale images already smaller than the requested width/height (default "false")
|
||||
--dpi
|
||||
Document rendering resolution in DPI (PDF, EPUB, etc.), 0 uses the default (300) (default "0")
|
||||
--format
|
||||
@@ -126,6 +128,8 @@ Commands:
|
||||
Image height (default "0")
|
||||
--fit
|
||||
Best fit for required width and height (default "false")
|
||||
--no-upscale
|
||||
Do not upscale images already smaller than the requested width/height (default "false")
|
||||
--dpi
|
||||
Document rendering resolution in DPI (PDF, EPUB, etc.), 0 uses the default (300) (default "0")
|
||||
--format
|
||||
@@ -156,6 +160,8 @@ Commands:
|
||||
Image height (default "0")
|
||||
--fit
|
||||
Best fit for required width and height (default "false")
|
||||
--no-upscale
|
||||
Do not upscale images already smaller than the requested width/height (default "false")
|
||||
--dpi
|
||||
Document rendering resolution in DPI (PDF, EPUB, etc.), 0 uses the default (300) (default "0")
|
||||
--filter
|
||||
|
||||
+5
-10
@@ -39,6 +39,8 @@ type Options struct {
|
||||
Height int
|
||||
// Best fit for required width and height
|
||||
Fit bool
|
||||
// Do not upscale images already smaller than the requested width/height
|
||||
NoUpscale bool
|
||||
// Document rendering resolution in DPI (PDF, EPUB, etc.); 0 uses the default
|
||||
DPI int
|
||||
// 0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos
|
||||
@@ -180,6 +182,7 @@ func (o Options) Args() []string {
|
||||
num("width", o.Width, def.Width)
|
||||
num("height", o.Height, def.Height)
|
||||
flag("fit", o.Fit)
|
||||
flag("no-upscale", o.NoUpscale)
|
||||
num("dpi", o.DPI, def.DPI)
|
||||
str("format", o.Format, def.Format)
|
||||
str("archive", o.Archive, def.Archive)
|
||||
@@ -360,11 +363,7 @@ func (c *Converter) Cover(file File) error {
|
||||
}
|
||||
|
||||
if c.Opts.Width > 0 || c.Opts.Height > 0 {
|
||||
if c.Opts.Fit {
|
||||
cover = fit(cover, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
} else {
|
||||
cover = resize(cover, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
}
|
||||
cover = c.resizeFit(cover)
|
||||
}
|
||||
|
||||
ext := c.Opts.Format
|
||||
@@ -409,11 +408,7 @@ func (c *Converter) Thumbnail(file File) error {
|
||||
}
|
||||
|
||||
if c.Opts.Width > 0 || c.Opts.Height > 0 {
|
||||
if c.Opts.Fit {
|
||||
cover = fit(cover, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
} else {
|
||||
cover = resize(cover, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
}
|
||||
cover = c.resizeFit(cover)
|
||||
} else {
|
||||
cover = resize(cover, 256, 0, filters[c.Opts.Filter])
|
||||
}
|
||||
|
||||
@@ -326,11 +326,7 @@ func (c *Converter) imageTransform(img image.Image) image.Image {
|
||||
var i = img
|
||||
|
||||
if c.Opts.Width > 0 || c.Opts.Height > 0 {
|
||||
if c.Opts.Fit {
|
||||
i = fit(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
} else {
|
||||
i = resize(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
}
|
||||
i = c.resizeFit(i)
|
||||
}
|
||||
|
||||
if c.Opts.Rotate > 0 {
|
||||
|
||||
@@ -86,6 +86,26 @@ func fit(img image.Image, width, height int, filter transform.ResampleFilter) *i
|
||||
return resize(img, dstW, dstH, filter)
|
||||
}
|
||||
|
||||
// withinBounds reports whether img already fits within width by height; a zero dimension is unbounded.
|
||||
func withinBounds(img image.Image, width, height int) bool {
|
||||
b := img.Bounds()
|
||||
|
||||
return (width == 0 || b.Dx() <= width) && (height == 0 || b.Dy() <= height)
|
||||
}
|
||||
|
||||
// resizeFit resizes img to the configured width/height, honoring Fit and NoUpscale.
|
||||
func (c *Converter) resizeFit(img image.Image) image.Image {
|
||||
if c.Opts.Fit {
|
||||
return fit(img, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
}
|
||||
|
||||
if c.Opts.NoUpscale && withinBounds(img, c.Opts.Width, c.Opts.Height) {
|
||||
return img
|
||||
}
|
||||
|
||||
return resize(img, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
}
|
||||
|
||||
func rotate(img image.Image, angle float64) *image.RGBA {
|
||||
return transform.Rotate(img, angle, &transform.RotationOptions{ResizeBounds: true, Pivot: &image.Point{}})
|
||||
}
|
||||
|
||||
+52
-1
@@ -114,17 +114,68 @@ func TestArgs(t *testing.T) {
|
||||
opts.Effort = 4
|
||||
opts.Lossless = true
|
||||
opts.Width = 1200
|
||||
opts.NoUpscale = true
|
||||
opts.DPI = 150
|
||||
opts.Grayscale = true
|
||||
opts.OutDir = "/out"
|
||||
|
||||
got := strings.Join(opts.Args(), " ")
|
||||
want := "--width 1200 --dpi 150 --format webp --quality 90 --effort 4 --lossless --grayscale --outdir /out"
|
||||
want := "--width 1200 --no-upscale --dpi 150 --format webp --quality 90 --effort 4 --lossless --grayscale --outdir /out"
|
||||
if got != want {
|
||||
t.Errorf("Args() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoUpscale(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
width := func(o Options) int {
|
||||
conv := New(o)
|
||||
files, err := conv.Files([]string{"testdata/test.cbz"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, file := range files {
|
||||
if err := conv.Convert(file); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return firstPage(t, conv, filepath.Join(tmpDir, "test.cbz")).Bounds().Dx()
|
||||
}
|
||||
|
||||
base := NewOptions()
|
||||
base.OutDir = tmpDir
|
||||
orig := width(base)
|
||||
|
||||
up := NewOptions()
|
||||
up.OutDir = tmpDir
|
||||
up.Width = orig * 2
|
||||
up.NoUpscale = true
|
||||
if got := width(up); got != orig {
|
||||
t.Errorf("NoUpscale should keep original width %d, got %d", orig, got)
|
||||
}
|
||||
|
||||
no := NewOptions()
|
||||
no.OutDir = tmpDir
|
||||
no.Width = orig * 2
|
||||
if got := width(no); got != orig*2 {
|
||||
t.Errorf("without NoUpscale should upscale to %d, got %d", orig*2, got)
|
||||
}
|
||||
|
||||
down := NewOptions()
|
||||
down.OutDir = tmpDir
|
||||
down.Width = orig / 2
|
||||
down.NoUpscale = true
|
||||
if got := width(down); got != orig/2 {
|
||||
t.Errorf("NoUpscale should still downscale to %d, got %d", orig/2, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertDPI(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
|
||||
@@ -25,6 +25,7 @@ func options() cbconvert.Options {
|
||||
opts.Height = iup.GetHandle("Height").GetInt("VALUE")
|
||||
opts.DPI = dpiValue(iup.GetHandle("DPI").GetAttribute("VALUE"))
|
||||
opts.Fit = iup.GetHandle("Fit").GetAttribute("VALUE") == "ON"
|
||||
opts.NoUpscale = iup.GetHandle("NoUpscale").GetAttribute("VALUE") == "ON"
|
||||
opts.Filter = iup.GetHandle("Filter").GetInt("VALUE") - 1
|
||||
opts.Quality = iup.GetHandle("Quality").GetInt("VALUE")
|
||||
switch opts.Format {
|
||||
|
||||
@@ -39,6 +39,7 @@ var settings = []setting{
|
||||
{"NoNonImage", kindBool, "OFF"},
|
||||
{"Combine", kindBool, "OFF"},
|
||||
{"Fit", kindBool, "OFF"},
|
||||
{"NoUpscale", kindBool, "OFF"},
|
||||
{"Lossless", kindBool, "OFF"},
|
||||
{"Grayscale", kindBool, "OFF"},
|
||||
{"OutDir", kindStr, ""},
|
||||
|
||||
@@ -359,6 +359,8 @@ func tabImage() iup.Ihandle {
|
||||
iup.Vbox(
|
||||
iup.Toggle(" Best Fit").SetHandle("Fit").
|
||||
SetAttributes(`TIP="Best fit for required width and height"`),
|
||||
iup.Toggle(" No Upscale").SetHandle("NoUpscale").
|
||||
SetAttribute("TIP", "Do not enlarge images already smaller than the requested size"),
|
||||
),
|
||||
iup.Vbox(
|
||||
iup.Label("Resize Filter:"),
|
||||
|
||||
@@ -5,6 +5,7 @@ go 1.26
|
||||
require (
|
||||
github.com/gen2brain/cbconvert v1.0.5-0.20260623161611-a5817c3ba5de
|
||||
github.com/schollz/progressbar/v3 v3.19.0
|
||||
golang.org/x/term v0.44.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -52,7 +53,6 @@ require (
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/term v0.44.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
@@ -192,6 +192,7 @@ func parseFlags() (cbconvert.Options, []string) {
|
||||
convert.IntVar(&opts.Width, "width", 0, "Image width")
|
||||
convert.IntVar(&opts.Height, "height", 0, "Image height")
|
||||
convert.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
||||
convert.BoolVar(&opts.NoUpscale, "no-upscale", false, "Do not upscale images already smaller than the requested width/height")
|
||||
convert.IntVar(&opts.DPI, "dpi", 0, "Document rendering resolution in DPI (PDF, EPUB, etc.), 0 uses the default (300)")
|
||||
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")
|
||||
@@ -220,6 +221,7 @@ func parseFlags() (cbconvert.Options, []string) {
|
||||
cover.IntVar(&opts.Width, "width", 0, "Image width")
|
||||
cover.IntVar(&opts.Height, "height", 0, "Image height")
|
||||
cover.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
||||
cover.BoolVar(&opts.NoUpscale, "no-upscale", false, "Do not upscale images already smaller than the requested width/height")
|
||||
cover.IntVar(&opts.DPI, "dpi", 0, "Document rendering resolution in DPI (PDF, EPUB, etc.), 0 uses the default (300)")
|
||||
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")
|
||||
@@ -235,6 +237,7 @@ func parseFlags() (cbconvert.Options, []string) {
|
||||
thumbnail.IntVar(&opts.Width, "width", 0, "Image width")
|
||||
thumbnail.IntVar(&opts.Height, "height", 0, "Image height")
|
||||
thumbnail.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
||||
thumbnail.BoolVar(&opts.NoUpscale, "no-upscale", false, "Do not upscale images already smaller than the requested width/height")
|
||||
thumbnail.IntVar(&opts.DPI, "dpi", 0, "Document rendering resolution in DPI (PDF, EPUB, etc.), 0 uses the default (300)")
|
||||
thumbnail.IntVar(&opts.Filter, "filter", 2, "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos")
|
||||
thumbnail.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
|
||||
@@ -256,7 +259,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", "dpi", "format", "archive", "zip-level", "quality", "effort", "lossless", "combine", "outfile", "filter", "no-cover", "no-rgb",
|
||||
order := []string{"width", "height", "fit", "no-upscale", "dpi", "format", "archive", "zip-level", "quality", "effort", "lossless", "combine", "outfile", "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)
|
||||
@@ -264,14 +267,14 @@ 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", "dpi", "format", "quality", "effort", "lossless", "filter", "outdir", "size", "recursive", "quiet"}
|
||||
order = []string{"width", "height", "fit", "no-upscale", "dpi", "format", "quality", "effort", "lossless", "filter", "outdir", "size", "recursive", "quiet"}
|
||||
for _, name := range order {
|
||||
f := cover.Lookup(name)
|
||||
fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
||||
fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\n thumbnail\n \tExtract cover thumbnail (freedesktop spec.)\n\n")
|
||||
order = []string{"width", "height", "fit", "dpi", "filter", "outdir", "outfile", "size", "recursive", "quiet"}
|
||||
order = []string{"width", "height", "fit", "no-upscale", "dpi", "filter", "outdir", "outfile", "size", "recursive", "quiet"}
|
||||
for _, name := range order {
|
||||
f := thumbnail.Lookup(name)
|
||||
fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
||||
|
||||
Reference in New Issue
Block a user