diff --git a/internal/converter/converter.go b/internal/converter/converter.go index 29fd6a0..f7edc51 100644 --- a/internal/converter/converter.go +++ b/internal/converter/converter.go @@ -122,6 +122,7 @@ func (c *Converter) InitParse() { c.AddBoolParam(&c.Options.AutoRotate, "autorotate", c.Options.AutoRotate, "Auto Rotate page when width > height") c.AddBoolParam(&c.Options.AutoSplitDoublePage, "autosplitdoublepage", c.Options.AutoSplitDoublePage, "Auto Split double page when width > height") c.AddBoolParam(&c.Options.KeepDoublePageIfSplit, "keepdoublepageifsplit", c.Options.KeepDoublePageIfSplit, "Keep the double page if split") + c.AddBoolParam(&c.Options.KeepSplitDoublePageAspect, "keepsplitdoublepageaspect", c.Options.KeepSplitDoublePageAspect, "Keep aspect of split part of a double page (best for landscape rendering)") c.AddBoolParam(&c.Options.NoBlankImage, "noblankimage", c.Options.NoBlankImage, "Remove blank image") c.AddBoolParam(&c.Options.Manga, "manga", c.Options.Manga, "Manga mode (right to left)") c.AddBoolParam(&c.Options.HasCover, "hascover", c.Options.HasCover, "Has cover. Indicate if your comic have a cover. The first page will be used as a cover and include after the title.") @@ -274,6 +275,11 @@ func (c *Converter) Parse() { if c.Options.AppleBookCompatibility { c.Options.AutoSplitDoublePage = true c.Options.KeepDoublePageIfSplit = false + c.Options.KeepSplitDoublePageAspect = true + } + + if c.Options.PortraitOnly { + c.Options.KeepSplitDoublePageAspect = false } } diff --git a/internal/converter/options/converter_options.go b/internal/converter/options/converter_options.go index 934159c..ec29fb4 100644 --- a/internal/converter/options/converter_options.go +++ b/internal/converter/options/converter_options.go @@ -38,6 +38,7 @@ type Options struct { AutoRotate bool `yaml:"auto_rotate"` AutoSplitDoublePage bool `yaml:"auto_split_double_page"` KeepDoublePageIfSplit bool `yaml:"keep_double_page_if_split"` + KeepSplitDoublePageAspect bool `yaml:"keep_split_double_page_aspect"` NoBlankImage bool `yaml:"no_blank_image"` Manga bool `yaml:"manga"` HasCover bool `yaml:"has_cover"` @@ -82,25 +83,24 @@ type Options struct { // New Initialize default options. func New() *Options { return &Options{ - Profile: "SR", - Quality: 85, - Grayscale: true, - Crop: true, - CropRatioLeft: 1, - CropRatioUp: 1, - CropRatioRight: 1, - CropRatioBottom: 3, - CropLimit: 10, - CropSkipIfLimitReached: true, - NoBlankImage: true, - HasCover: true, - KeepDoublePageIfSplit: true, - SortPathMode: 1, - ForegroundColor: "000", - BackgroundColor: "FFF", - Format: "jpeg", - TitlePage: 1, - profiles: profiles.New(), + Profile: "SR", + Quality: 85, + Grayscale: true, + Crop: true, + CropRatioLeft: 1, + CropRatioUp: 1, + CropRatioRight: 1, + CropRatioBottom: 3, + NoBlankImage: true, + HasCover: true, + KeepDoublePageIfSplit: true, + KeepSplitDoublePageAspect: true, + SortPathMode: 1, + ForegroundColor: "000", + BackgroundColor: "FFF", + Format: "jpeg", + TitlePage: 1, + profiles: profiles.New(), } } @@ -179,6 +179,7 @@ func (o *Options) MarshalJSON() ([]byte, error) { out["autosplitdoublepage"] = o.AutoSplitDoublePage if o.AutoSplitDoublePage { out["keepdoublepageifsplit"] = o.KeepDoublePageIfSplit + out["keepsplitdoublepageaspect"] = o.KeepSplitDoublePageAspect } } if o.LimitMb != 0 { @@ -285,6 +286,7 @@ func (o *Options) ShowConfig() string { {"Auto rotate", o.AutoRotate, true}, {"Auto split double page", o.AutoSplitDoublePage, o.PortraitOnly || !o.AppleBookCompatibility}, {"Keep double page if split", o.KeepDoublePageIfSplit, (o.PortraitOnly || !o.AppleBookCompatibility) && o.AutoSplitDoublePage}, + {"Keep split double page aspect", o.KeepSplitDoublePageAspect, (o.PortraitOnly || !o.AppleBookCompatibility) && o.AutoSplitDoublePage}, {"No blank image", o.NoBlankImage, true}, {"Manga", o.Manga, true}, {"Has cover", o.HasCover, true}, diff --git a/internal/epub/imagefilters/epub_image_filters_autocrop.go b/internal/epub/imagefilters/epub_image_filters_autocrop.go index aa51b85..dac2c9f 100644 --- a/internal/epub/imagefilters/epub_image_filters_autocrop.go +++ b/internal/epub/imagefilters/epub_image_filters_autocrop.go @@ -28,10 +28,8 @@ type cutRatioOptions struct { func findMargin(img image.Image, bounds image.Rectangle, cutRatio cutRatioOptions, limit int, skipIfLimitReached bool) image.Rectangle { imgArea := bounds - maxCropX, maxCropY := imgArea.Dx()*limit/100, imgArea.Dy()*limit/100 - LEFT: - for x, maxCrop := imgArea.Min.X, maxCropX; x < imgArea.Max.X && (limit == 0 || maxCrop > 0); x, maxCrop = x+1, maxCrop-1 { + for x := imgArea.Min.X; x < imgArea.Max.X; x++ { allowNonBlank := imgArea.Dy() * cutRatio.Left / 100 for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ { if !colorIsBlank(img.At(x, y)) { @@ -42,13 +40,10 @@ LEFT: } } imgArea.Min.X++ - if limit > 0 && maxCrop == 1 && skipIfLimitReached { - return bounds - } } UP: - for y, maxCrop := imgArea.Min.Y, maxCropY; y < imgArea.Max.Y && (limit == 0 || maxCrop > 0); y, maxCrop = y+1, maxCrop-1 { + for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ { allowNonBlank := imgArea.Dx() * cutRatio.Up / 100 for x := imgArea.Min.X; x < imgArea.Max.X; x++ { if !colorIsBlank(img.At(x, y)) { @@ -59,13 +54,10 @@ UP: } } imgArea.Min.Y++ - if limit > 0 && maxCrop == 1 && skipIfLimitReached { - return bounds - } } RIGHT: - for x, maxCrop := imgArea.Max.X-1, maxCropX; x >= imgArea.Min.X && (limit == 0 || maxCrop > 0); x, maxCrop = x-1, maxCrop-1 { + for x := imgArea.Max.X - 1; x >= imgArea.Min.X; x-- { allowNonBlank := imgArea.Dy() * cutRatio.Right / 100 for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ { if !colorIsBlank(img.At(x, y)) { @@ -76,13 +68,10 @@ RIGHT: } } imgArea.Max.X-- - if limit > 0 && maxCrop == 1 && skipIfLimitReached { - return bounds - } } BOTTOM: - for y, maxCrop := imgArea.Max.Y-1, maxCropY; y >= imgArea.Min.Y && (limit == 0 || maxCrop > 0); y, maxCrop = y-1, maxCrop-1 { + for y := imgArea.Max.Y - 1; y >= imgArea.Min.Y; y-- { allowNonBlank := imgArea.Dx() * cutRatio.Bottom / 100 for x := imgArea.Min.X; x < imgArea.Max.X; x++ { if !colorIsBlank(img.At(x, y)) { @@ -93,10 +82,42 @@ BOTTOM: } } imgArea.Max.Y-- - if limit > 0 && maxCrop == 1 && skipIfLimitReached { - return bounds - } } + // no limit or blankImage + if limit == 0 || imgArea.Dx() == 0 || imgArea.Dy() == 0 { + return imgArea + } + + exceedX, exceedY := limitExceed(bounds, imgArea, limit) + if skipIfLimitReached && (exceedX > 0 || exceedY > 0) { + return bounds + } + + imgArea.Min.X, imgArea.Max.X = correctLine(imgArea.Min.X, imgArea.Max.X, bounds.Min.X, bounds.Max.X, exceedX) + imgArea.Min.Y, imgArea.Max.Y = correctLine(imgArea.Min.Y, imgArea.Max.Y, bounds.Min.Y, bounds.Max.Y, exceedY) + return imgArea } + +func limitExceed(bounds, newBounds image.Rectangle, limit int) (int, int) { + return bounds.Dx() - newBounds.Dx() - bounds.Dx()*limit/100, bounds.Dy() - newBounds.Dy() - bounds.Dy()*limit/100 +} + +func correctLine(min, max, bMin, bMax, exceed int) (int, int) { + if exceed <= 0 { + return min, max + } + + min -= exceed / 2 + max += exceed / 2 + if min < bMin { + max += bMin - min + min = bMin + } + if max > bMax { + min -= max - bMax + max = bMax + } + return min, max +} diff --git a/internal/epub/imageprocessor/epub_image_processor.go b/internal/epub/imageprocessor/epub_image_processor.go index 12c4943..2a5f098 100644 --- a/internal/epub/imageprocessor/epub_image_processor.go +++ b/internal/epub/imageprocessor/epub_image_processor.go @@ -178,7 +178,9 @@ func (e *EPUBImageProcessor) transformImage(input *task, part int, right bool) * src := input.Image srcBounds := src.Bounds() - if part > 0 { + // In portrait only, we don't need to keep aspect ratio between each split. + // We first cut, the crop. + if part > 0 && !e.Image.KeepSplitDoublePageAspect { g.Add(epubimagefilters.CropSplitDoublePage(right)) } @@ -205,11 +207,18 @@ func (e *EPUBImageProcessor) transformImage(input *task, part int, right bool) * } } + // With landscape support, we need to keep aspect ratio between each split + // We first crop, then cut + if part > 0 && e.Image.KeepSplitDoublePageAspect { + g.Add(epubimagefilters.CropSplitDoublePage(right)) + } + dstBounds := g.Bounds(src.Bounds()) // Original && Cropped version need to landscape oriented - isDoublePage := srcBounds.Dx() > srcBounds.Dy() && dstBounds.Dx() > dstBounds.Dy() + // Only part 0 can be a double page + isDoublePage := part == 0 && srcBounds.Dx() > srcBounds.Dy() && dstBounds.Dx() > dstBounds.Dy() - if part == 0 && e.Image.AutoRotate && isDoublePage { + if e.Image.AutoRotate && isDoublePage { g.Add(gift.Rotate90()) } diff --git a/internal/epub/options/epub_options.go b/internal/epub/options/epub_options.go index 6715ee8..2d2367a 100644 --- a/internal/epub/options/epub_options.go +++ b/internal/epub/options/epub_options.go @@ -22,23 +22,24 @@ type View struct { } type Image struct { - Crop *Crop - Quality int - Brightness int - Contrast int - AutoContrast bool - AutoRotate bool - AutoSplitDoublePage bool - KeepDoublePageIfSplit bool - NoBlankImage bool - Manga bool - HasCover bool - View *View - GrayScale bool - GrayScaleMode int - Resize bool - Format string - AppleBookCompatibility bool + Crop *Crop + Quality int + Brightness int + Contrast int + AutoContrast bool + AutoRotate bool + AutoSplitDoublePage bool + KeepDoublePageIfSplit bool + KeepSplitDoublePageAspect bool + NoBlankImage bool + Manga bool + HasCover bool + View *View + GrayScale bool + GrayScaleMode int + Resize bool + Format string + AppleBookCompatibility bool } type Options struct { diff --git a/main.go b/main.go index 0181f87..23c238d 100644 --- a/main.go +++ b/main.go @@ -134,16 +134,17 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s Limit: cmd.Options.CropLimit, SkipIfLimitReached: cmd.Options.CropSkipIfLimitReached, }, - Quality: cmd.Options.Quality, - Brightness: cmd.Options.Brightness, - Contrast: cmd.Options.Contrast, - AutoContrast: cmd.Options.AutoContrast, - AutoRotate: cmd.Options.AutoRotate, - AutoSplitDoublePage: cmd.Options.AutoSplitDoublePage, - KeepDoublePageIfSplit: cmd.Options.KeepDoublePageIfSplit, - NoBlankImage: cmd.Options.NoBlankImage, - Manga: cmd.Options.Manga, - HasCover: cmd.Options.HasCover, + Quality: cmd.Options.Quality, + Brightness: cmd.Options.Brightness, + Contrast: cmd.Options.Contrast, + AutoContrast: cmd.Options.AutoContrast, + AutoRotate: cmd.Options.AutoRotate, + AutoSplitDoublePage: cmd.Options.AutoSplitDoublePage, + KeepDoublePageIfSplit: cmd.Options.KeepDoublePageIfSplit, + KeepSplitDoublePageAspect: cmd.Options.KeepSplitDoublePageAspect, + NoBlankImage: cmd.Options.NoBlankImage, + Manga: cmd.Options.Manga, + HasCover: cmd.Options.HasCover, View: &epuboptions.View{ Width: profile.Width, Height: profile.Height,