From 7ce25bb23441100ec591ddb8aa95cbd826d31595 Mon Sep 17 00:00:00 2001 From: celogeek <65178+celogeek@users.noreply.github.com> Date: Tue, 9 May 2023 17:40:09 +0200 Subject: [PATCH] create auto contrast filter This filter improve contrast and make the comics more readable --- internal/converter/converter.go | 3 + .../converter/options/converter_options.go | 2 + .../epub_image_filters_autocontrast.go | 90 +++++++++++++++++++ .../imageprocessor/epub_image_processor.go | 6 ++ internal/epub/options/epub_options.go | 1 + main.go | 30 ++----- 6 files changed, 110 insertions(+), 22 deletions(-) create mode 100644 internal/epub/imagefilters/epub_image_filters_autocontrast.go diff --git a/internal/converter/converter.go b/internal/converter/converter.go index a95e974..0dcb1bb 100644 --- a/internal/converter/converter.go +++ b/internal/converter/converter.go @@ -116,6 +116,7 @@ func (c *Converter) InitParse() { c.AddIntParam(&c.Options.CropRatioBottom, "crop-ratio-bottom", c.Options.CropRatioBottom, "Crop ratio bottom: ratio of pixels allow to be non blank while cutting on the bottom.") c.AddIntParam(&c.Options.Brightness, "brightness", c.Options.Brightness, "Brightness readjustement: between -100 and 100, > 0 lighter, < 0 darker") c.AddIntParam(&c.Options.Contrast, "contrast", c.Options.Contrast, "Contrast readjustement: between -100 and 100, > 0 more contrast, < 0 less contrast") + c.AddBoolParam(&c.Options.AutoContrast, "autocontrast", c.Options.AutoContrast, "Improve contrast automatically") 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.NoBlankImage, "noblankimage", c.Options.NoBlankImage, "Remove blank image") @@ -227,6 +228,7 @@ func (c *Converter) Parse() { } if c.Options.Auto { + c.Options.AutoContrast = true c.Options.AutoRotate = true c.Options.AutoSplitDoublePage = true } @@ -256,6 +258,7 @@ func (c *Converter) Parse() { c.Options.Crop = false c.Options.Brightness = 0 c.Options.Contrast = 0 + c.Options.AutoContrast = false c.Options.AutoRotate = false c.Options.NoBlankImage = false c.Options.NoResize = true diff --git a/internal/converter/options/converter_options.go b/internal/converter/options/converter_options.go index e9bcf9d..55bc561 100644 --- a/internal/converter/options/converter_options.go +++ b/internal/converter/options/converter_options.go @@ -32,6 +32,7 @@ type Options struct { CropRatioBottom int `yaml:"crop_ratio_bottom"` Brightness int `yaml:"brightness"` Contrast int `yaml:"contrast"` + AutoContrast bool `yaml:"auto_contrast"` AutoRotate bool `yaml:"auto_rotate"` AutoSplitDoublePage bool `yaml:"auto_split_double_page"` NoBlankImage bool `yaml:"no_blank_image"` @@ -203,6 +204,7 @@ func (o *Options) ShowConfig() string { {"CropRatio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom), o.Crop}, {"Brightness", o.Brightness, o.Brightness != 0}, {"Contrast", o.Contrast, o.Contrast != 0}, + {"AutoContrast", o.AutoContrast, true}, {"AutoRotate", o.AutoRotate, true}, {"AutoSplitDoublePage", o.AutoSplitDoublePage, true}, {"NoBlankImage", o.NoBlankImage, true}, diff --git a/internal/epub/imagefilters/epub_image_filters_autocontrast.go b/internal/epub/imagefilters/epub_image_filters_autocontrast.go new file mode 100644 index 0000000..95fbfb9 --- /dev/null +++ b/internal/epub/imagefilters/epub_image_filters_autocontrast.go @@ -0,0 +1,90 @@ +package epubimagefilters + +import ( + "image" + "image/color" + "image/draw" + + "github.com/disintegration/gift" +) + +// Automatically improve contrast +func AutoContrast() *autocontrast { + return &autocontrast{} +} + +type autocontrast struct { +} + +// compute the color number between 0 and 1 that hold half of the pixel +func (f *autocontrast) mean(src image.Image) float32 { + bucket := map[uint32]int{} + for x := src.Bounds().Min.X; x < src.Bounds().Max.X; x++ { + for y := src.Bounds().Min.Y; y < src.Bounds().Max.Y; y++ { + v, _, _, _ := color.GrayModel.Convert(src.At(x, y)).RGBA() + bucket[v]++ + } + } + + // calculate color idx + var colorIdx uint32 + { + // limit to half of the pixel + limit := src.Bounds().Dx() * src.Bounds().Dy() / 2 + // loop on all color from 0 to 65536 + for colorIdx = 0; colorIdx < 1<<16; colorIdx++ { + if limit-bucket[colorIdx] < 0 { + break + } + limit -= bucket[colorIdx] + } + } + // return the color idx between 0 and 1 + return float32(colorIdx) / (1 << 16) +} + +// ensure value stay into 0 to 1 bound +func (f *autocontrast) cap(v float32) float32 { + if v < 0 { + return 0 + } + if v > 1 { + return 1 + } + return v +} + +// power of 2 for float32 +func (f *autocontrast) pow2(v float32) float32 { + return v * v +} + +// Draw into the dst after applying the filter +func (f *autocontrast) Draw(dst draw.Image, src image.Image, options *gift.Options) { + // half of the pixel has this color idx + colorMean := f.mean(src) + + // if colorMean > 0.5, it means the color is mostly clear + // in that case we will add a lot more darkness other light + // compute dark factor + d := f.pow2(colorMean) + // compute light factor + l := f.pow2(1 - colorMean) + + gift.ColorFunc(func(r0, g0, b0, a0 float32) (r float32, g float32, b float32, a float32) { + // convert to gray color the source RGB + y := 0.299*r0 + 0.587*g0 + 0.114*b0 + + // compute a curve from dark and light factor applying to the color + c := (1 - d) + (d+l)*y + + // applying the coef + return f.cap(r0 * c), f.cap(g0 * c), f.cap(b0 * c), a0 + }).Draw(dst, src, options) +} + +// Bounds calculates the appropriate bounds of an image after applying the filter. +func (*autocontrast) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) { + dstBounds = srcBounds + return +} diff --git a/internal/epub/imageprocessor/epub_image_processor.go b/internal/epub/imageprocessor/epub_image_processor.go index 8836490..308466a 100644 --- a/internal/epub/imageprocessor/epub_image_processor.go +++ b/internal/epub/imageprocessor/epub_image_processor.go @@ -197,6 +197,12 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image. filters = append(filters, gift.Rotate90()) } + if e.Image.AutoContrast { + f := epubimagefilters.AutoContrast() + filters = append(filters, f) + splitFilters = append(splitFilters, f) + } + if e.Image.Contrast != 0 { f := gift.Contrast(float32(e.Image.Contrast)) filters = append(filters, f) diff --git a/internal/epub/options/epub_options.go b/internal/epub/options/epub_options.go index 1cc3515..b926b00 100644 --- a/internal/epub/options/epub_options.go +++ b/internal/epub/options/epub_options.go @@ -26,6 +26,7 @@ type Image struct { Quality int Brightness int Contrast int + AutoContrast bool AutoRotate bool AutoSplitDoublePage bool NoBlankImage bool diff --git a/main.go b/main.go index abfeafc..9a72cf4 100644 --- a/main.go +++ b/main.go @@ -114,35 +114,21 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s DryVerbose: cmd.Options.DryVerbose, Quiet: cmd.Options.Quiet, Image: &epuboptions.Image{ - Quality: cmd.Options.Quality, - GrayScale: cmd.Options.Grayscale, - GrayScaleMode: cmd.Options.GrayscaleMode, - Crop: &epuboptions.Crop{ - Enabled: cmd.Options.Crop, - Left: cmd.Options.CropRatioLeft, - Up: cmd.Options.CropRatioUp, - Right: cmd.Options.CropRatioRight, - Bottom: cmd.Options.CropRatioBottom, - }, + Crop: &epuboptions.Crop{Enabled: cmd.Options.Crop, Left: cmd.Options.CropRatioLeft, Up: cmd.Options.CropRatioUp, Right: cmd.Options.CropRatioRight, Bottom: cmd.Options.CropRatioBottom}, + Quality: cmd.Options.Quality, Brightness: cmd.Options.Brightness, Contrast: cmd.Options.Contrast, + AutoContrast: cmd.Options.AutoContrast, AutoRotate: cmd.Options.AutoRotate, AutoSplitDoublePage: cmd.Options.AutoSplitDoublePage, NoBlankImage: cmd.Options.NoBlankImage, Manga: cmd.Options.Manga, HasCover: cmd.Options.HasCover, - View: &epuboptions.View{ - Width: profile.Width, - Height: profile.Height, - AspectRatio: cmd.Options.AspectRatio, - PortraitOnly: cmd.Options.PortraitOnly, - Color: epuboptions.Color{ - Foreground: cmd.Options.ForegroundColor, - Background: cmd.Options.BackgroundColor, - }, - }, - Resize: !cmd.Options.NoResize, - Format: cmd.Options.Format, + View: &epuboptions.View{Width: profile.Width, Height: profile.Height, AspectRatio: cmd.Options.AspectRatio, PortraitOnly: cmd.Options.PortraitOnly, Color: epuboptions.Color{Foreground: cmd.Options.ForegroundColor, Background: cmd.Options.BackgroundColor}}, + GrayScale: cmd.Options.Grayscale, + GrayScaleMode: cmd.Options.GrayscaleMode, + Resize: !cmd.Options.NoResize, + Format: cmd.Options.Format, }, }).Write(); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err)