diff --git a/internal/converter/converter.go b/internal/converter/converter.go index fc40ce9..3b47242 100644 --- a/internal/converter/converter.go +++ b/internal/converter/converter.go @@ -84,6 +84,12 @@ func (c *Converter) AddIntParam(p *int, name string, value int, usage string) { c.order = append(c.order, converterOrderName{value: name}) } +// Add an float parameter +func (c *Converter) AddFloatParam(p *float64, name string, value float64, usage string) { + c.Cmd.Float64Var(p, name, value, usage) + c.order = append(c.order, converterOrderName{value: name}) +} + // Add a boolean parameter func (c *Converter) AddBoolParam(p *bool, name string, value bool, usage string) { c.Cmd.BoolVar(p, name, value, usage) @@ -121,6 +127,7 @@ func (c *Converter) InitParse() { c.AddStringParam(&c.Options.BackgroundColor, "background-color", c.Options.BackgroundColor, "Background color in hexa format RGB. Black=000, White=FFF, Light Gray=DDD, Dark Gray=777") c.AddBoolParam(&c.Options.NoResize, "noresize", c.Options.NoResize, "Do not reduce image size if exceed device size") c.AddStringParam(&c.Options.Format, "format", c.Options.Format, "Format of output images: jpeg (lossy), png (lossless)") + c.AddFloatParam(&c.Options.AspectRatio, "aspect-ratio", c.Options.AspectRatio, "Aspect ratio (height/width) of the output\n -1 = same as device\n 0 = same as source\n1.6 = amazon advice for kindle") c.AddSection("Default config") c.AddBoolParam(&c.Options.Show, "show", false, "Show your default parameters") @@ -351,6 +358,11 @@ func (c *Converter) Validate() error { return errors.New("format should be jpeg or png") } + // Aspect Ratio + if c.Options.AspectRatio < 0 && c.Options.AspectRatio != -1 { + return errors.New("aspect ratio should be: -1, 0, > 0") + } + return nil } diff --git a/internal/converter/options/converter_options.go b/internal/converter/options/converter_options.go index d5e8796..c685e6a 100644 --- a/internal/converter/options/converter_options.go +++ b/internal/converter/options/converter_options.go @@ -21,28 +21,29 @@ type Options struct { Title string `yaml:"-"` // Config - Profile string `yaml:"profile"` - Quality int `yaml:"quality"` - Grayscale bool `yaml:"grayscale"` - Crop bool `yaml:"crop"` - CropRatioLeft int `yaml:"crop_ratio_left"` - CropRatioUp int `yaml:"crop_ratio_up"` - CropRatioRight int `yaml:"crop_ratio_right"` - CropRatioBottom int `yaml:"crop_ratio_bottom"` - Brightness int `yaml:"brightness"` - Contrast int `yaml:"contrast"` - AutoRotate bool `yaml:"auto_rotate"` - AutoSplitDoublePage bool `yaml:"auto_split_double_page"` - NoBlankImage bool `yaml:"no_blank_image"` - Manga bool `yaml:"manga"` - HasCover bool `yaml:"has_cover"` - LimitMb int `yaml:"limit_mb"` - StripFirstDirectoryFromToc bool `yaml:"strip_first_directory_from_toc"` - SortPathMode int `yaml:"sort_path_mode"` - ForegroundColor string `yaml:"foreground_color"` - BackgroundColor string `yaml:"background_color"` - NoResize bool `yaml:"noresize"` - Format string `yaml:"format"` + Profile string `yaml:"profile"` + Quality int `yaml:"quality"` + Grayscale bool `yaml:"grayscale"` + Crop bool `yaml:"crop"` + CropRatioLeft int `yaml:"crop_ratio_left"` + CropRatioUp int `yaml:"crop_ratio_up"` + CropRatioRight int `yaml:"crop_ratio_right"` + CropRatioBottom int `yaml:"crop_ratio_bottom"` + Brightness int `yaml:"brightness"` + Contrast int `yaml:"contrast"` + AutoRotate bool `yaml:"auto_rotate"` + AutoSplitDoublePage bool `yaml:"auto_split_double_page"` + NoBlankImage bool `yaml:"no_blank_image"` + Manga bool `yaml:"manga"` + HasCover bool `yaml:"has_cover"` + LimitMb int `yaml:"limit_mb"` + StripFirstDirectoryFromToc bool `yaml:"strip_first_directory_from_toc"` + SortPathMode int `yaml:"sort_path_mode"` + ForegroundColor string `yaml:"foreground_color"` + BackgroundColor string `yaml:"background_color"` + NoResize bool `yaml:"noresize"` + Format string `yaml:"format"` + AspectRatio float64 `yaml:"aspect_ratio"` // Default Config Show bool `yaml:"-"` @@ -94,6 +95,7 @@ func New() *Options { BackgroundColor: "FFF", NoResize: false, Format: "jpeg", + AspectRatio: 0, profiles: profiles.New(), } } @@ -145,7 +147,7 @@ func (o *Options) LoadConfig() error { // Get current settings for fields that can be saved func (o *Options) ShowConfig() string { - var profileDesc, viewDesc string + var profileDesc string profile := o.GetProfile() if profile != nil { profileDesc = fmt.Sprintf( @@ -155,13 +157,6 @@ func (o *Options) ShowConfig() string { profile.Width, profile.Height, ) - - perfectWidth, perfectHeight := profile.PerfectDim() - viewDesc = fmt.Sprintf( - "%dx%d", - perfectWidth, - perfectHeight, - ) } sortpathmode := "" @@ -174,6 +169,13 @@ func (o *Options) ShowConfig() string { sortpathmode = "path=alphanum, file=alphanum" } + aspectRatio := "auto" + if o.AspectRatio > 0 { + aspectRatio = fmt.Sprintf("1:%.02f", o.AspectRatio) + } else if o.AspectRatio < 0 { + aspectRatio = fmt.Sprintf("1:%0.2f (device)", float64(profile.Height)/float64(profile.Width)) + } + var b strings.Builder for _, v := range []struct { Key string @@ -181,8 +183,6 @@ func (o *Options) ShowConfig() string { Condition bool }{ {"Profile", profileDesc, true}, - {"ViewRatio", fmt.Sprintf("1:%s", strings.TrimRight(fmt.Sprintf("%f", profiles.PerfectRatio), "0")), true}, - {"View", viewDesc, true}, {"Format", o.Format, true}, {"Quality", o.Quality, o.Format == "jpeg"}, {"Grayscale", o.Grayscale, true}, @@ -201,6 +201,7 @@ func (o *Options) ShowConfig() string { {"Foreground Color", fmt.Sprintf("#%s", o.ForegroundColor), true}, {"Background Color", fmt.Sprintf("#%s", o.BackgroundColor), true}, {"Resize", !o.NoResize, true}, + {"Aspect Ratio", aspectRatio, true}, } { if v.Condition { b.WriteString(fmt.Sprintf("\n %-26s: %v", v.Key, v.Value)) diff --git a/internal/converter/profiles/converter_profiles.go b/internal/converter/profiles/converter_profiles.go index 413cebf..cb4df00 100644 --- a/internal/converter/profiles/converter_profiles.go +++ b/internal/converter/profiles/converter_profiles.go @@ -15,21 +15,6 @@ type Profile struct { Height int } -// Recommended ratio of image for perfect rendering Portrait or Landscape. -const PerfectRatio = 1.6 - -// Compute best dimension based on device size -func (p Profile) PerfectDim() (int, int) { - width, height := float64(p.Width), float64(p.Height) - perfectWidth, perfectHeight := height/PerfectRatio, width*PerfectRatio - if perfectWidth > width { - perfectWidth = width - } else { - perfectHeight = height - } - return int(perfectWidth), int(perfectHeight) -} - type Profiles []Profile // Initialize list of all supported profiles. diff --git a/internal/epub/epub.go b/internal/epub/epub.go index 380082a..80b87b0 100644 --- a/internal/epub/epub.go +++ b/internal/epub/epub.go @@ -6,6 +6,7 @@ package epub import ( "archive/zip" "fmt" + "math" "os" "path/filepath" "regexp" @@ -193,6 +194,52 @@ func (e *ePub) getTree(images []*epubimage.Image, skip_files bool) string { return c.WriteString("") } +func (e *ePub) computeAspectRatio(epubParts []*epubPart) float64 { + var ( + bestAspectRatio float64 + bestAspectRatioCount int + aspectRatio = map[float64]int{} + ) + + trunc := func(v float64) float64 { + return float64(math.Round(v*10000)) / 10000 + } + + for _, p := range epubParts { + aspectRatio[trunc(p.Cover.OriginalAspectRatio)]++ + for _, i := range p.Images { + aspectRatio[trunc(i.OriginalAspectRatio)]++ + } + } + + for k, v := range aspectRatio { + if v > bestAspectRatioCount { + bestAspectRatio, bestAspectRatioCount = k, v + } + } + + return bestAspectRatio +} + +func (e *ePub) computeViewPort(epubParts []*epubPart) { + if e.Image.View.AspectRatio == -1 { + return //keep device size + } + + // readjusting view port + bestAspectRatio := e.Image.View.AspectRatio + if bestAspectRatio == 0 { + bestAspectRatio = e.computeAspectRatio(epubParts) + } + + viewWidth, viewHeight := int(float64(e.Image.View.Height)/bestAspectRatio), int(float64(e.Image.View.Width)*bestAspectRatio) + if viewWidth > e.Image.View.Width { + e.Image.View.Height = viewHeight + } else { + e.Image.View.Width = viewWidth + } +} + // create the zip func (e *ePub) Write() error { type zipContent struct { @@ -231,6 +278,7 @@ func (e *ePub) Write() error { Quiet: e.Quiet, }) + e.computeViewPort(epubParts) for i, part := range epubParts { ext := filepath.Ext(e.Output) suffix := "" diff --git a/internal/epub/image/epub_image.go b/internal/epub/image/epub_image.go index f6d5e73..046ce03 100644 --- a/internal/epub/image/epub_image.go +++ b/internal/epub/image/epub_image.go @@ -10,18 +10,19 @@ import ( ) type Image struct { - Id int - Part int - Raw image.Image - Width int - Height int - IsCover bool - IsBlank bool - DoublePage bool - Path string - Name string - Position string - Format string + Id int + Part int + Raw image.Image + Width int + Height int + IsCover bool + IsBlank bool + DoublePage bool + Path string + Name string + Position string + Format string + OriginalAspectRatio float64 } // key name of the blank plage after the image diff --git a/internal/epub/imageprocessor/epub_image_processor.go b/internal/epub/imageprocessor/epub_image_processor.go index e308255..e8d3bd3 100644 --- a/internal/epub/imageprocessor/epub_image_processor.go +++ b/internal/epub/imageprocessor/epub_image_processor.go @@ -85,17 +85,18 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) { } img := &epubimage.Image{ - Id: input.Id, - Part: part, - Raw: raw, - Width: dst.Bounds().Dx(), - Height: dst.Bounds().Dy(), - IsCover: input.Id == 0 && part == 0, - IsBlank: dst.Bounds().Dx() == 1 && dst.Bounds().Dy() == 1, - DoublePage: part == 0 && src.Bounds().Dx() > src.Bounds().Dy(), - Path: input.Path, - Name: input.Name, - Format: e.Image.Format, + Id: input.Id, + Part: part, + Raw: raw, + Width: dst.Bounds().Dx(), + Height: dst.Bounds().Dy(), + IsCover: input.Id == 0 && part == 0, + IsBlank: dst.Bounds().Dx() == 1 && dst.Bounds().Dy() == 1, + DoublePage: part == 0 && src.Bounds().Dx() > src.Bounds().Dy(), + Path: input.Path, + Name: input.Name, + Format: e.Image.Format, + OriginalAspectRatio: float64(src.Bounds().Dy()) / float64(src.Bounds().Dx()), } if err = imgStorage.Add(img.EPUBImgPath(), dst, e.Image.Quality); err != nil { diff --git a/internal/epub/options/epub_options.go b/internal/epub/options/epub_options.go index 427852e..7f6cc42 100644 --- a/internal/epub/options/epub_options.go +++ b/internal/epub/options/epub_options.go @@ -16,6 +16,7 @@ type Color struct { type View struct { Width, Height int + AspectRatio float64 Color Color } diff --git a/main.go b/main.go index d354def..0db43e5 100644 --- a/main.go +++ b/main.go @@ -99,7 +99,6 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s fmt.Fprintln(os.Stderr, cmd.Options) profile := cmd.Options.GetProfile() - perfectWidth, perfectHeight := profile.PerfectDim() if err := epub.New(&epuboptions.Options{ Input: cmd.Options.Input, @@ -131,8 +130,9 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s Manga: cmd.Options.Manga, HasCover: cmd.Options.HasCover, View: &epuboptions.View{ - Width: perfectWidth, - Height: perfectHeight, + Width: profile.Width, + Height: profile.Height, + AspectRatio: cmd.Options.AspectRatio, Color: epuboptions.Color{ Foreground: cmd.Options.ForegroundColor, Background: cmd.Options.BackgroundColor,