From f7f8680eb3e0d18d822a4b3ed07dbc03465f8bb0 Mon Sep 17 00:00:00 2001 From: celogeek <65178+celogeek@users.noreply.github.com> Date: Mon, 6 May 2024 17:24:31 +0200 Subject: [PATCH 1/5] fix doublePage issue when part crop are also a double page --- internal/epub/imageprocessor/epub_image_processor.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/epub/imageprocessor/epub_image_processor.go b/internal/epub/imageprocessor/epub_image_processor.go index 12c4943..3f40d38 100644 --- a/internal/epub/imageprocessor/epub_image_processor.go +++ b/internal/epub/imageprocessor/epub_image_processor.go @@ -207,9 +207,10 @@ func (e *EPUBImageProcessor) transformImage(input *task, part int, right bool) * 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()) } From f9ebe71b9e4d97d540f71ddb010d9aad6f73be87 Mon Sep 17 00:00:00 2001 From: celogeek <65178+celogeek@users.noreply.github.com> Date: Tue, 7 May 2024 08:55:42 +0200 Subject: [PATCH 2/5] crop double page after autocrop fix landscape display when each side can be cut differently. A doublepage is 1 big image, and should be cut as one. --- internal/epub/imageprocessor/epub_image_processor.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/epub/imageprocessor/epub_image_processor.go b/internal/epub/imageprocessor/epub_image_processor.go index 3f40d38..4dd811c 100644 --- a/internal/epub/imageprocessor/epub_image_processor.go +++ b/internal/epub/imageprocessor/epub_image_processor.go @@ -178,10 +178,6 @@ func (e *EPUBImageProcessor) transformImage(input *task, part int, right bool) * src := input.Image srcBounds := src.Bounds() - if part > 0 { - g.Add(epubimagefilters.CropSplitDoublePage(right)) - } - // Lookup for margin if crop is enable or if we want to remove blank image if e.Image.Crop.Enabled || e.Image.NoBlankImage { f := epubimagefilters.AutoCrop( @@ -205,6 +201,10 @@ func (e *EPUBImageProcessor) transformImage(input *task, part int, right bool) * } } + if part > 0 { + g.Add(epubimagefilters.CropSplitDoublePage(right)) + } + dstBounds := g.Bounds(src.Bounds()) // Original && Cropped version need to landscape oriented // Only part 0 can be a double page From 27d617640a1e1df3bb1469911253e1d3fda83870 Mon Sep 17 00:00:00 2001 From: celogeek <65178+celogeek@users.noreply.github.com> Date: Wed, 8 May 2024 18:49:01 +0200 Subject: [PATCH 3/5] detect empty page --- .../epub_image_filters_autocrop.go | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) 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 +} From 62fc520ebb2463391b39121acd873f8a303ef3c4 Mon Sep 17 00:00:00 2001 From: celogeek <65178+celogeek@users.noreply.github.com> Date: Thu, 9 May 2024 12:28:48 +0200 Subject: [PATCH 4/5] cut more each side of doublepage split in portrait only --- internal/epub/imageprocessor/epub_image_processor.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/epub/imageprocessor/epub_image_processor.go b/internal/epub/imageprocessor/epub_image_processor.go index 4dd811c..46ae5cf 100644 --- a/internal/epub/imageprocessor/epub_image_processor.go +++ b/internal/epub/imageprocessor/epub_image_processor.go @@ -178,6 +178,12 @@ func (e *EPUBImageProcessor) transformImage(input *task, part int, right bool) * src := input.Image srcBounds := src.Bounds() + // In portrait only, we don't need to keep aspect ratio between each split. + // We first cut, the crop. + if part > 0 && e.Image.View.PortraitOnly { + g.Add(epubimagefilters.CropSplitDoublePage(right)) + } + // Lookup for margin if crop is enable or if we want to remove blank image if e.Image.Crop.Enabled || e.Image.NoBlankImage { f := epubimagefilters.AutoCrop( @@ -201,7 +207,9 @@ func (e *EPUBImageProcessor) transformImage(input *task, part int, right bool) * } } - if part > 0 { + // With landscape support, we need to keep aspect ratio between each split + // We first crop, then cut + if part > 0 && !e.Image.View.PortraitOnly { g.Add(epubimagefilters.CropSplitDoublePage(right)) } From 87faac877cfb3475e7aa673d92b7cd0965aaab7a Mon Sep 17 00:00:00 2001 From: celogeek <65178+celogeek@users.noreply.github.com> Date: Thu, 9 May 2024 15:14:54 +0200 Subject: [PATCH 5/5] add option to not preserve aspect on split double page --- internal/converter/converter.go | 6 +++ .../converter/options/converter_options.go | 40 ++++++++++--------- .../imageprocessor/epub_image_processor.go | 4 +- internal/epub/options/epub_options.go | 35 ++++++++-------- main.go | 21 +++++----- 5 files changed, 58 insertions(+), 48 deletions(-) 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/imageprocessor/epub_image_processor.go b/internal/epub/imageprocessor/epub_image_processor.go index 46ae5cf..2a5f098 100644 --- a/internal/epub/imageprocessor/epub_image_processor.go +++ b/internal/epub/imageprocessor/epub_image_processor.go @@ -180,7 +180,7 @@ func (e *EPUBImageProcessor) transformImage(input *task, part int, right bool) * // In portrait only, we don't need to keep aspect ratio between each split. // We first cut, the crop. - if part > 0 && e.Image.View.PortraitOnly { + if part > 0 && !e.Image.KeepSplitDoublePageAspect { g.Add(epubimagefilters.CropSplitDoublePage(right)) } @@ -209,7 +209,7 @@ 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.View.PortraitOnly { + if part > 0 && e.Image.KeepSplitDoublePageAspect { g.Add(epubimagefilters.CropSplitDoublePage(right)) } 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,