From e8c028d2764f833f87f742a0bc7c0b811a0f0c26 Mon Sep 17 00:00:00 2001 From: celogeek <65178+celogeek@users.noreply.github.com> Date: Sat, 31 Dec 2022 19:42:20 +0100 Subject: [PATCH] simplify: use gift package --- go.mod | 1 + go.sum | 2 + internal/epub/image_processing.go | 97 +++++++++++-- .../palette.go => epub/palettes.go} | 2 +- internal/imageconverter/algo.go | 29 ---- internal/imageconverter/core.go | 134 ------------------ main.go | 65 ++++----- 7 files changed, 113 insertions(+), 217 deletions(-) rename internal/{imageconverter/palette.go => epub/palettes.go} (97%) delete mode 100644 internal/imageconverter/algo.go delete mode 100644 internal/imageconverter/core.go diff --git a/go.mod b/go.mod index 9751425..fedc804 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/celogeek/go-comic-converter go 1.19 require ( + github.com/disintegration/gift v1.2.1 github.com/gofrs/uuid v4.3.1+incompatible github.com/nwaples/rardecode v1.1.3 github.com/raff/pdfreader v0.0.0-20220308062436-033e8ac577f0 diff --git a/go.sum b/go.sum index 1dc2b0b..a9d5617 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= +github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI= github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= diff --git a/internal/epub/image_processing.go b/internal/epub/image_processing.go index 2968ccd..bebbe23 100644 --- a/internal/epub/image_processing.go +++ b/internal/epub/image_processing.go @@ -4,6 +4,9 @@ import ( "archive/zip" "bytes" "fmt" + "image" + "image/color" + "image/jpeg" "io" "io/fs" "os" @@ -13,7 +16,7 @@ import ( "strings" "sync" - "github.com/celogeek/go-comic-converter/internal/imageconverter" + "github.com/disintegration/gift" "github.com/nwaples/rardecode" pdfimage "github.com/raff/pdfreader/image" @@ -33,6 +36,57 @@ type imageTask struct { Reader io.ReadCloser } +func colorIsBlank(c color.Color) bool { + g := color.GrayModel.Convert(c).(color.Gray) + return g.Y >= 0xf0 +} + +func findMarging(img image.Image) image.Rectangle { + imgArea := img.Bounds() + +LEFT: + for x := imgArea.Min.X; x < imgArea.Max.X; x++ { + for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ { + if !colorIsBlank(img.At(x, y)) { + break LEFT + } + } + imgArea.Min.X++ + } + +UP: + for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ { + for x := imgArea.Min.X; x < imgArea.Max.X; x++ { + if !colorIsBlank(img.At(x, y)) { + break UP + } + } + imgArea.Min.Y++ + } + +RIGHT: + for x := imgArea.Max.X - 1; x >= imgArea.Min.X; x-- { + for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ { + if !colorIsBlank(img.At(x, y)) { + break RIGHT + } + } + imgArea.Max.X-- + } + +BOTTOM: + for y := imgArea.Max.Y - 1; y >= imgArea.Min.Y; y-- { + for x := imgArea.Min.X; x < imgArea.Max.X; x++ { + if !colorIsBlank(img.At(x, y)) { + break BOTTOM + } + } + imgArea.Max.Y-- + } + + return imgArea +} + func LoadImages(path string, options *ImageOptions) ([]*Image, error) { images := make([]*Image, 0) @@ -69,29 +123,48 @@ func LoadImages(path string, options *ImageOptions) ([]*Image, error) { // processing wg := &sync.WaitGroup{} bar := NewBar(imageCount, "Processing", 1, 2) + for i := 0; i < runtime.NumCPU(); i++ { wg.Add(1) go func() { defer wg.Done() + for img := range imageInput { - data, w, h := imageconverter.Convert( - img.Reader, - options.Crop, - options.ViewWidth, - options.ViewHeight, - options.Quality, - options.Algo, - options.Palette, + // Decode image + src, _, err := image.Decode(img.Reader) + if err != nil { + panic(err) + } + + // prepare filter + g := gift.New( + gift.Crop(findMarging(src)), + gift.ResizeToFit(options.ViewWidth, options.ViewHeight, gift.LanczosResampling), + // gift.Gamma(1.8), + // gift.Sigmoid(0.5, 5), ) + g.SetParallelization(false) + + // Convert image + dst := image.NewPaletted(g.Bounds(src.Bounds()), options.Palette) + g.Draw(dst, src) + + // Encode image + b := bytes.NewBuffer([]byte{}) + err = jpeg.Encode(b, dst, &jpeg.Options{Quality: options.Quality}) + if err != nil { + panic(err) + } + name := fmt.Sprintf("OEBPS/Images/%d.jpg", img.Id) if img.Id == 0 { name = "OEBPS/Images/cover.jpg" } imageOutput <- &Image{ img.Id, - newImageData(name, data), - w, - h, + newImageData(name, b.Bytes()), + dst.Bounds().Dx(), + dst.Bounds().Dy(), } } }() diff --git a/internal/imageconverter/palette.go b/internal/epub/palettes.go similarity index 97% rename from internal/imageconverter/palette.go rename to internal/epub/palettes.go index eb64108..1335378 100644 --- a/internal/imageconverter/palette.go +++ b/internal/epub/palettes.go @@ -1,4 +1,4 @@ -package imageconverter +package epub import "image/color" diff --git a/internal/imageconverter/algo.go b/internal/imageconverter/algo.go deleted file mode 100644 index e6e2090..0000000 --- a/internal/imageconverter/algo.go +++ /dev/null @@ -1,29 +0,0 @@ -package imageconverter - -import ( - "image/color" - "sort" -) - -var ALGO_GRAY = map[string]func(color.Color, color.Palette) color.Gray{ - "default": func(c color.Color, p color.Palette) color.Gray { - return p.Convert(c).(color.Gray) - }, - "mean": func(c color.Color, p color.Palette) color.Gray { - r, g, b, _ := c.RGBA() - y := float64(r+g+b) / 3 - return p.Convert(color.Gray16{Y: uint16(y)}).(color.Gray) - }, - "luma": func(c color.Color, p color.Palette) color.Gray { - r, g, b, _ := c.RGBA() - y := (0.2126*float64(r) + 0.7152*float64(g) + 0.0722*float64(b)) - return p.Convert(color.Gray16{Y: uint16(y)}).(color.Gray) - }, - "luster": func(c color.Color, p color.Palette) color.Gray { - r, g, b, _ := c.RGBA() - arr := []float64{float64(r), float64(g), float64(b)} - sort.Float64s(arr) - y := (arr[0] + arr[2]) / 2 - return p.Convert(color.Gray16{Y: uint16(y)}).(color.Gray) - }, -} diff --git a/internal/imageconverter/core.go b/internal/imageconverter/core.go deleted file mode 100644 index 7c27992..0000000 --- a/internal/imageconverter/core.go +++ /dev/null @@ -1,134 +0,0 @@ -package imageconverter - -import ( - "bytes" - "image" - "image/color" - "image/jpeg" - "io" - - "golang.org/x/image/draw" -) - -func Load(reader io.ReadCloser, algo string, palette color.Palette) *image.Gray { - defer reader.Close() - img, _, err := image.Decode(reader) - if err != nil { - panic(err) - } - algoFunc, ok := ALGO_GRAY[algo] - if !ok { - panic("unknown algo") - } - - grayImg := image.NewGray(img.Bounds()) - for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ { - for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ { - grayImg.SetGray(x, y, algoFunc(img.At(x, y), palette)) - } - } - - return grayImg -} - -func isBlank(c color.Color) bool { - r, g, b, _ := c.RGBA() - return r > 60000 && g > 60000 && b > 60000 -} - -func CropMarging(img *image.Gray) *image.Gray { - imgArea := img.Bounds() - -LEFT: - for x := imgArea.Min.X; x < imgArea.Max.X; x++ { - for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ { - if !isBlank(img.At(x, y)) { - break LEFT - } - } - imgArea.Min.X++ - } - -UP: - for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ { - for x := imgArea.Min.X; x < imgArea.Max.X; x++ { - if !isBlank(img.At(x, y)) { - break UP - } - } - imgArea.Min.Y++ - } - -RIGHT: - for x := imgArea.Max.X - 1; x >= imgArea.Min.X; x-- { - for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ { - if !isBlank(img.At(x, y)) { - break RIGHT - } - } - imgArea.Max.X-- - } - -BOTTOM: - for y := imgArea.Max.Y - 1; y >= imgArea.Min.Y; y-- { - for x := imgArea.Min.X; x < imgArea.Max.X; x++ { - if !isBlank(img.At(x, y)) { - break BOTTOM - } - } - imgArea.Max.Y-- - } - - return img.SubImage(imgArea).(*image.Gray) -} - -func Resize(img *image.Gray, w, h int) *image.Gray { - dim := img.Bounds() - origWidth := dim.Dx() - origHeight := dim.Dy() - - if origWidth == 0 || origHeight == 0 { - newImg := image.NewGray(image.Rectangle{ - image.Point{0, 0}, - image.Point{w, h}, - }) - draw.Draw(newImg, newImg.Bounds(), image.NewUniform(color.White), newImg.Bounds().Min, draw.Src) - return newImg - } - - width, height := origWidth*h/origHeight, origHeight*w/origWidth - - if width > w { - width = w - } - if height > h { - height = h - } - - newImg := image.NewGray(image.Rectangle{ - Min: image.Point{0, 0}, - Max: image.Point{width, height}, - }) - - draw.BiLinear.Scale(newImg, newImg.Bounds(), img, img.Bounds(), draw.Src, nil) - - return newImg -} - -func Get(img *image.Gray, quality int) []byte { - b := bytes.NewBuffer([]byte{}) - err := jpeg.Encode(b, img, &jpeg.Options{Quality: quality}) - if err != nil { - panic(err) - } - return b.Bytes() -} - -func Convert(reader io.ReadCloser, crop bool, w, h int, quality int, algo string, palette color.Palette) ([]byte, int, int) { - img := Load(reader, algo, palette) - if crop { - img = CropMarging(img) - } - img = Resize(img, w, h) - return Get(img, quality), img.Bounds().Dx(), img.Bounds().Dy() -} diff --git a/main.go b/main.go index ead575e..6f025a1 100644 --- a/main.go +++ b/main.go @@ -8,8 +8,6 @@ import ( "path/filepath" "strings" - "github.com/celogeek/go-comic-converter/internal/imageconverter" - "github.com/celogeek/go-comic-converter/internal/epub" ) @@ -23,31 +21,31 @@ type Profile struct { var Profiles = []Profile{ // Kindle - {"K1", "Kindle 1", 600, 670, imageconverter.PALETTE_4}, - {"K11", "Kindle 11", 1072, 1448, imageconverter.PALETTE_16}, - {"K2", "Kindle 2", 600, 670, imageconverter.PALETTE_15}, - {"K34", "Kindle Keyboard/Touch", 600, 800, imageconverter.PALETTE_16}, - {"K578", "Kindle", 600, 800, imageconverter.PALETTE_16}, - {"KDX", "Kindle DX/DXG", 824, 1000, imageconverter.PALETTE_16}, - {"KPW", "Kindle Paperwhite 1/2", 758, 1024, imageconverter.PALETTE_16}, - {"KV", "Kindle Paperwhite 3/4/Voyage/Oasis", 1072, 1448, imageconverter.PALETTE_16}, - {"KPW5", "Kindle Paperwhite 5/Signature Edition", 1236, 1648, imageconverter.PALETTE_16}, - {"KO", "Kindle Oasis 2/3", 1264, 1680, imageconverter.PALETTE_16}, - {"KS", "Kindle Scribe", 1860, 2480, imageconverter.PALETTE_16}, + {"K1", "Kindle 1", 600, 670, epub.PALETTE_4}, + {"K11", "Kindle 11", 1072, 1448, epub.PALETTE_16}, + {"K2", "Kindle 2", 600, 670, epub.PALETTE_15}, + {"K34", "Kindle Keyboard/Touch", 600, 800, epub.PALETTE_16}, + {"K578", "Kindle", 600, 800, epub.PALETTE_16}, + {"KDX", "Kindle DX/DXG", 824, 1000, epub.PALETTE_16}, + {"KPW", "Kindle Paperwhite 1/2", 758, 1024, epub.PALETTE_16}, + {"KV", "Kindle Paperwhite 3/4/Voyage/Oasis", 1072, 1448, epub.PALETTE_16}, + {"KPW5", "Kindle Paperwhite 5/Signature Edition", 1236, 1648, epub.PALETTE_16}, + {"KO", "Kindle Oasis 2/3", 1264, 1680, epub.PALETTE_16}, + {"KS", "Kindle Scribe", 1860, 2480, epub.PALETTE_16}, // Kobo - {"KoMT", "Kobo Mini/Touch", 600, 800, imageconverter.PALETTE_16}, - {"KoG", "Kobo Glo", 768, 1024, imageconverter.PALETTE_16}, - {"KoGHD", "Kobo Glo HD", 1072, 1448, imageconverter.PALETTE_16}, - {"KoA", "Kobo Aura", 758, 1024, imageconverter.PALETTE_16}, - {"KoAHD", "Kobo Aura HD", 1080, 1440, imageconverter.PALETTE_16}, - {"KoAH2O", "Kobo Aura H2O", 1080, 1430, imageconverter.PALETTE_16}, - {"KoAO", "Kobo Aura ONE", 1404, 1872, imageconverter.PALETTE_16}, - {"KoN", "Kobo Nia", 758, 1024, imageconverter.PALETTE_16}, - {"KoC", "Kobo Clara HD/Kobo Clara 2E", 1072, 1448, imageconverter.PALETTE_16}, - {"KoL", "Kobo Libra H2O/Kobo Libra 2", 1264, 1680, imageconverter.PALETTE_16}, - {"KoF", "Kobo Forma", 1440, 1920, imageconverter.PALETTE_16}, - {"KoS", "Kobo Sage", 1440, 1920, imageconverter.PALETTE_16}, - {"KoE", "Kobo Elipsa", 1404, 1872, imageconverter.PALETTE_16}, + {"KoMT", "Kobo Mini/Touch", 600, 800, epub.PALETTE_16}, + {"KoG", "Kobo Glo", 768, 1024, epub.PALETTE_16}, + {"KoGHD", "Kobo Glo HD", 1072, 1448, epub.PALETTE_16}, + {"KoA", "Kobo Aura", 758, 1024, epub.PALETTE_16}, + {"KoAHD", "Kobo Aura HD", 1080, 1440, epub.PALETTE_16}, + {"KoAH2O", "Kobo Aura H2O", 1080, 1430, epub.PALETTE_16}, + {"KoAO", "Kobo Aura ONE", 1404, 1872, epub.PALETTE_16}, + {"KoN", "Kobo Nia", 758, 1024, epub.PALETTE_16}, + {"KoC", "Kobo Clara HD/Kobo Clara 2E", 1072, 1448, epub.PALETTE_16}, + {"KoL", "Kobo Libra H2O/Kobo Libra 2", 1264, 1680, epub.PALETTE_16}, + {"KoF", "Kobo Forma", 1440, 1920, epub.PALETTE_16}, + {"KoS", "Kobo Sage", 1440, 1920, epub.PALETTE_16}, + {"KoE", "Kobo Elipsa", 1404, 1872, epub.PALETTE_16}, } var ProfilesIdx = map[string]int{} @@ -65,7 +63,6 @@ type Option struct { Title string Quality int NoCrop bool - Algo string LimitMb int } @@ -94,7 +91,6 @@ Options: Title : %s Quality : %d Crop : %v - Algo : %s LimitMb : %s `, o.Input, @@ -104,7 +100,6 @@ Options: o.Title, o.Quality, !o.NoCrop, - o.Algo, limitmb, ) } @@ -120,10 +115,6 @@ func main() { p.Description, )) } - availableAlgo := make([]string, 0) - for a := range imageconverter.ALGO_GRAY { - availableAlgo = append(availableAlgo, a) - } opt := &Option{} flag.StringVar(&opt.Input, "input", "", "Source of comic to convert: directory, cbz, zip, cbr, rar, pdf") @@ -133,7 +124,6 @@ func main() { flag.StringVar(&opt.Title, "title", "", "Title of the epub") flag.IntVar(&opt.Quality, "quality", 85, "Quality of the image") flag.BoolVar(&opt.NoCrop, "nocrop", false, "Disable cropping") - flag.StringVar(&opt.Algo, "algo", "default", fmt.Sprintf("Algo for RGB to Grayscale: %s", strings.Join(availableAlgo, ", "))) flag.IntVar(&opt.LimitMb, "limitmb", 0, "Limit size of the ePub: Default nolimit (0), Minimum 20") flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage of %s:\n", filepath.Base(os.Args[0])) @@ -203,12 +193,6 @@ func main() { opt.Title = filepath.Base(defaultOutput[0 : len(defaultOutput)-len(ext)]) } - if _, ok := imageconverter.ALGO_GRAY[opt.Algo]; !ok { - fmt.Fprintln(os.Stderr, "algo doesn't exists") - flag.Usage() - os.Exit(1) - } - fmt.Fprintln(os.Stderr, opt) if err := epub.NewEpub(&epub.EpubOptions{ @@ -222,7 +206,6 @@ func main() { ViewHeight: profile.Height, Quality: opt.Quality, Crop: !opt.NoCrop, - Algo: opt.Algo, Palette: profile.Palette, }, }).Write(); err != nil {