diff --git a/internal/epub/imageprocessor/epub_image_processor.go b/internal/epubimageprocessor/epubimageprocessor.go similarity index 59% rename from internal/epub/imageprocessor/epub_image_processor.go rename to internal/epubimageprocessor/epubimageprocessor.go index f0fde37..fd6273d 100644 --- a/internal/epub/imageprocessor/epub_image_processor.go +++ b/internal/epubimageprocessor/epubimageprocessor.go @@ -6,55 +6,54 @@ package epubimageprocessor import ( "fmt" "image" - "image/color" "image/draw" "os" "sync" - epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image" - epubimagefilters "github.com/celogeek/go-comic-converter/v2/internal/epub/imagefilters" - epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options" - epubprogress "github.com/celogeek/go-comic-converter/v2/internal/epub/progress" - epubzip "github.com/celogeek/go-comic-converter/v2/internal/epub/zip" + "github.com/celogeek/go-comic-converter/v2/internal/epubimage" + "github.com/celogeek/go-comic-converter/v2/internal/epubimagefilters" + "github.com/celogeek/go-comic-converter/v2/internal/epubprogress" + "github.com/celogeek/go-comic-converter/v2/internal/epubzip" + "github.com/celogeek/go-comic-converter/v2/pkg/epuboptions" "github.com/disintegration/gift" ) type EPUBImageProcessor struct { - *epuboptions.Options + options *epuboptions.EPUBOptions } -func New(o *epuboptions.Options) *EPUBImageProcessor { +func New(o *epuboptions.EPUBOptions) *EPUBImageProcessor { return &EPUBImageProcessor{o} } // extract and convert images -func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) { - images = make([]*epubimage.Image, 0) +func (e *EPUBImageProcessor) Load() (images []*epubimage.EPUBImage, err error) { + images = make([]*epubimage.EPUBImage, 0) imageCount, imageInput, err := e.load() if err != nil { return nil, err } // dry run, skip convertion - if e.Dry { + if e.options.Dry { for img := range imageInput { - images = append(images, &epubimage.Image{ + images = append(images, &epubimage.EPUBImage{ Id: img.Id, Path: img.Path, Name: img.Name, - Format: e.Image.Format, + Format: e.options.Image.Format, }) } return images, nil } - imageOutput := make(chan *epubimage.Image) + imageOutput := make(chan *epubimage.EPUBImage) // processing bar := epubprogress.New(epubprogress.Options{ - Quiet: e.Quiet, - Json: e.Json, + Quiet: e.options.Quiet, + Json: e.options.Json, Max: imageCount, Description: "Processing", CurrentJob: 1, @@ -62,17 +61,17 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) { }) wg := &sync.WaitGroup{} - imgStorage, err := epubzip.NewEPUBZipStorageImageWriter(e.ImgStorage(), e.Image.Format) + imgStorage, err := epubzip.NewImageWriter(e.options.Temp(), e.options.Image.Format) if err != nil { bar.Close() return nil, err } wr := 50 - if e.Image.Format == "png" { + if e.options.Image.Format == "png" { wr = 100 } - for i := 0; i < e.WorkersRatio(wr); i++ { + for i := 0; i < e.options.WorkersRatio(wr); i++ { wg.Add(1) go func() { defer wg.Done() @@ -86,7 +85,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) { raw = dst } - img := &epubimage.Image{ + img := &epubimage.EPUBImage{ Id: input.Id, Part: part, Raw: raw, @@ -97,7 +96,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) { DoublePage: part == 0 && src.Bounds().Dx() > src.Bounds().Dy(), Path: input.Path, Name: input.Name, - Format: e.Image.Format, + Format: e.options.Image.Format, OriginalAspectRatio: float64(src.Bounds().Dy()) / float64(src.Bounds().Dx()), Error: input.Error, } @@ -105,12 +104,12 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) { // do not keep double page if requested if !img.IsCover && img.DoublePage && - e.Options.Image.AutoSplitDoublePage && - !e.Options.Image.KeepDoublePageIfSplitted { + e.options.Image.AutoSplitDoublePage && + !e.options.Image.KeepDoublePageIfSplitted { continue } - if err = imgStorage.Add(img.EPUBImgPath(), dst, e.Image.Quality); err != nil { + if err = imgStorage.Add(img.EPUBImgPath(), dst, e.options.Image.Quality); err != nil { bar.Close() fmt.Fprintf(os.Stderr, "error with %s: %s", input.Name, err) os.Exit(1) @@ -131,7 +130,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) { if img.Part == 0 { bar.Add(1) } - if e.Image.NoBlankImage && img.IsBlank { + if e.options.Image.NoBlankImage && img.IsBlank { continue } images = append(images, img) @@ -146,7 +145,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) { } func (e *EPUBImageProcessor) createImage(src image.Image, r image.Rectangle) draw.Image { - if e.Options.Image.GrayScale { + if e.options.Image.GrayScale { return image.NewGray(r) } @@ -183,13 +182,13 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image. var images []image.Image // Lookup for margin if crop is enable or if we want to remove blank image - if e.Image.Crop.Enabled || e.Image.NoBlankImage { + if e.options.Image.Crop.Enabled || e.options.Image.NoBlankImage { f := epubimagefilters.AutoCrop( src, - e.Image.Crop.Left, - e.Image.Crop.Up, - e.Image.Crop.Right, - e.Image.Crop.Bottom, + e.options.Image.Crop.Left, + e.options.Image.Crop.Up, + e.options.Image.Crop.Right, + e.options.Image.Crop.Bottom, ) // detect if blank image @@ -197,42 +196,42 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image. isBlank := size.Dx() == 0 && size.Dy() == 0 // crop is enable or if blank image with noblankimage options - if e.Image.Crop.Enabled || (e.Image.NoBlankImage && isBlank) { + if e.options.Image.Crop.Enabled || (e.options.Image.NoBlankImage && isBlank) { filters = append(filters, f) splitFilters = append(splitFilters, f) } } - if e.Image.AutoRotate && src.Bounds().Dx() > src.Bounds().Dy() { + if e.options.Image.AutoRotate && src.Bounds().Dx() > src.Bounds().Dy() { filters = append(filters, gift.Rotate90()) } - if e.Image.AutoContrast { + if e.options.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)) + if e.options.Image.Contrast != 0 { + f := gift.Contrast(float32(e.options.Image.Contrast)) filters = append(filters, f) splitFilters = append(splitFilters, f) } - if e.Image.Brightness != 0 { - f := gift.Brightness(float32(e.Image.Brightness)) + if e.options.Image.Brightness != 0 { + f := gift.Brightness(float32(e.options.Image.Brightness)) filters = append(filters, f) splitFilters = append(splitFilters, f) } - if e.Image.Resize { - f := gift.ResizeToFit(e.Image.View.Width, e.Image.View.Height, gift.LanczosResampling) + if e.options.Image.Resize { + f := gift.ResizeToFit(e.options.Image.View.Width, e.options.Image.View.Height, gift.LanczosResampling) filters = append(filters, f) } - if e.Image.GrayScale { + if e.options.Image.GrayScale { var f gift.Filter - switch e.Image.GrayScaleMode { + switch e.options.Image.GrayScaleMode { case 1: // average f = gift.ColorFunc(func(r0, g0, b0, a0 float32) (r float32, g float32, b float32, a float32) { y := (r0 + g0 + b0) / 3 @@ -261,7 +260,7 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image. } // auto split off - if !e.Image.AutoSplitDoublePage { + if !e.options.Image.AutoSplitDoublePage { return images } @@ -271,16 +270,16 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image. } // cover - if e.Image.HasCover && srcId == 0 { + if e.options.Image.HasCover && srcId == 0 { return images } // convert double page - for _, b := range []bool{e.Image.Manga, !e.Image.Manga} { + for _, b := range []bool{e.options.Image.Manga, !e.options.Image.Manga} { g := gift.New(splitFilters...) g.Add(epubimagefilters.CropSplitDoublePage(b)) - if e.Image.Resize { - g.Add(gift.ResizeToFit(e.Image.View.Width, e.Image.View.Height, gift.LanczosResampling)) + if e.options.Image.Resize { + g.Add(gift.ResizeToFit(e.options.Image.View.Width, e.options.Image.View.Height, gift.LanczosResampling)) } dst := e.createImage(src, g.Bounds(src.Bounds())) g.Draw(dst, src) @@ -289,55 +288,3 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image. return images } - -type CoverTitleDataOptions struct { - Src image.Image - Name string - Text string - Align string - PctWidth int - PctMargin int - MaxFontSize int - BorderSize int -} - -func (e *EPUBImageProcessor) Cover16LevelOfGray(bounds image.Rectangle) draw.Image { - return image.NewPaletted(bounds, color.Palette{ - color.Gray{0x00}, - color.Gray{0x11}, - color.Gray{0x22}, - color.Gray{0x33}, - color.Gray{0x44}, - color.Gray{0x55}, - color.Gray{0x66}, - color.Gray{0x77}, - color.Gray{0x88}, - color.Gray{0x99}, - color.Gray{0xAA}, - color.Gray{0xBB}, - color.Gray{0xCC}, - color.Gray{0xDD}, - color.Gray{0xEE}, - color.Gray{0xFF}, - }) -} - -// create a title page with the cover -func (e *EPUBImageProcessor) CoverTitleData(o *CoverTitleDataOptions) (*epubzip.ZipImage, error) { - // Create a blur version of the cover - g := gift.New(epubimagefilters.CoverTitle(o.Text, o.Align, o.PctWidth, o.PctMargin, o.MaxFontSize, o.BorderSize)) - var dst draw.Image - if o.Name == "cover" && e.Image.GrayScale { - dst = e.Cover16LevelOfGray(o.Src.Bounds()) - } else { - dst = e.createImage(o.Src, g.Bounds(o.Src.Bounds())) - } - g.Draw(dst, o.Src) - - return epubzip.CompressImage( - fmt.Sprintf("OEBPS/Images/%s.%s", o.Name, e.Image.Format), - e.Image.Format, - dst, - e.Image.Quality, - ) -} diff --git a/internal/epubimageprocessor/epubimageprocessor_cover_title_data.go b/internal/epubimageprocessor/epubimageprocessor_cover_title_data.go new file mode 100644 index 0000000..de40d6e --- /dev/null +++ b/internal/epubimageprocessor/epubimageprocessor_cover_title_data.go @@ -0,0 +1,61 @@ +package epubimageprocessor + +import ( + "fmt" + "image" + "image/color" + "image/draw" + + "github.com/celogeek/go-comic-converter/v2/internal/epubimagefilters" + "github.com/celogeek/go-comic-converter/v2/internal/epubzip" + "github.com/disintegration/gift" +) + +type CoverTitleDataOptions struct { + Src image.Image + Name string + Text string + Align string + PctWidth int + PctMargin int + MaxFontSize int + BorderSize int +} + +// create a title page with the cover +func (e *EPUBImageProcessor) CoverTitleData(o *CoverTitleDataOptions) (*epubzip.EPUBZipImage, error) { + // Create a blur version of the cover + g := gift.New(epubimagefilters.CoverTitle(o.Text, o.Align, o.PctWidth, o.PctMargin, o.MaxFontSize, o.BorderSize)) + var dst draw.Image + if o.Name == "cover" && e.options.Image.GrayScale { + // 16 shade of gray + dst = image.NewPaletted(o.Src.Bounds(), color.Palette{ + color.Gray{0x00}, + color.Gray{0x11}, + color.Gray{0x22}, + color.Gray{0x33}, + color.Gray{0x44}, + color.Gray{0x55}, + color.Gray{0x66}, + color.Gray{0x77}, + color.Gray{0x88}, + color.Gray{0x99}, + color.Gray{0xAA}, + color.Gray{0xBB}, + color.Gray{0xCC}, + color.Gray{0xDD}, + color.Gray{0xEE}, + color.Gray{0xFF}, + }) + } else { + dst = e.createImage(o.Src, g.Bounds(o.Src.Bounds())) + } + g.Draw(dst, o.Src) + + return epubzip.CompressImage( + fmt.Sprintf("OEBPS/Images/%s.%s", o.Name, e.options.Image.Format), + e.options.Image.Format, + dst, + e.options.Image.Quality, + ) +} diff --git a/internal/epub/imageprocessor/epub_image_processor_loader.go b/internal/epubimageprocessor/epubimageprocessor_loader.go similarity index 89% rename from internal/epub/imageprocessor/epub_image_processor_loader.go rename to internal/epubimageprocessor/epubimageprocessor_loader.go index 3a563a8..8d4b345 100644 --- a/internal/epub/imageprocessor/epub_image_processor_loader.go +++ b/internal/epubimageprocessor/epubimageprocessor_loader.go @@ -51,7 +51,7 @@ func (e *EPUBImageProcessor) isSupportedImage(path string) bool { // Load images from input func (e *EPUBImageProcessor) load() (totalImages int, output chan *tasks, err error) { - fi, err := os.Stat(e.Input) + fi, err := os.Stat(e.options.Input) if err != nil { return } @@ -60,7 +60,7 @@ func (e *EPUBImageProcessor) load() (totalImages int, output chan *tasks, err er if fi.IsDir() { return e.loadDir() } else { - switch ext := strings.ToLower(filepath.Ext(e.Input)); ext { + switch ext := strings.ToLower(filepath.Ext(e.options.Input)); ext { case ".cbz", ".zip": return e.loadCbz() case ".cbr", ".rar": @@ -101,7 +101,7 @@ func (e *EPUBImageProcessor) corruptedImage(path, name string) image.Image { func (e *EPUBImageProcessor) loadDir() (totalImages int, output chan *tasks, err error) { images := make([]string, 0) - input := filepath.Clean(e.Input) + input := filepath.Clean(e.options.Input) err = filepath.WalkDir(input, func(path string, d fs.DirEntry, err error) error { if err != nil { return err @@ -123,7 +123,7 @@ func (e *EPUBImageProcessor) loadDir() (totalImages int, output chan *tasks, err return } - sort.Sort(sortpath.By(images, e.SortPathMode)) + sort.Sort(sortpath.By(images, e.options.SortPathMode)) // Queue all file with id type job struct { @@ -139,16 +139,16 @@ func (e *EPUBImageProcessor) loadDir() (totalImages int, output chan *tasks, err }() // read in parallel and get an image - output = make(chan *tasks, e.Workers) + output = make(chan *tasks, e.options.Workers) wg := &sync.WaitGroup{} - for j := 0; j < e.WorkersRatio(50); j++ { + for j := 0; j < e.options.WorkersRatio(50); j++ { wg.Add(1) go func() { defer wg.Done() for job := range jobs { var img image.Image var err error - if !e.Dry { + if !e.options.Dry { var f *os.File f, err = os.Open(job.Path) if err == nil { @@ -188,7 +188,7 @@ func (e *EPUBImageProcessor) loadDir() (totalImages int, output chan *tasks, err // load a zip file that include images func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *tasks, err error) { - r, err := zip.OpenReader(e.Input) + r, err := zip.OpenReader(e.options.Input) if err != nil { return } @@ -212,7 +212,7 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *tasks, err for _, img := range images { names = append(names, img.Name) } - sort.Sort(sortpath.By(names, e.SortPathMode)) + sort.Sort(sortpath.By(names, e.options.SortPathMode)) indexedNames := make(map[string]int) for i, name := range names { @@ -231,16 +231,16 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *tasks, err } }() - output = make(chan *tasks, e.Workers) + output = make(chan *tasks, e.options.Workers) wg := &sync.WaitGroup{} - for j := 0; j < e.WorkersRatio(50); j++ { + for j := 0; j < e.options.WorkersRatio(50); j++ { wg.Add(1) go func() { defer wg.Done() for job := range jobs { var img image.Image var err error - if !e.Dry { + if !e.options.Dry { var f io.ReadCloser f, err = job.F.Open() if err == nil { @@ -275,7 +275,7 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *tasks, err // load a rar file that include images func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *tasks, err error) { var isSolid bool - files, err := rardecode.List(e.Input) + files, err := rardecode.List(e.options.Input) if err != nil { return } @@ -296,7 +296,7 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *tasks, err return } - sort.Sort(sortpath.By(names, e.SortPathMode)) + sort.Sort(sortpath.By(names, e.options.SortPathMode)) indexedNames := make(map[string]int) for i, name := range names { @@ -312,10 +312,10 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *tasks, err jobs := make(chan *job) go func() { defer close(jobs) - if isSolid && !e.Dry { - r, rerr := rardecode.OpenReader(e.Input) + if isSolid && !e.options.Dry { + r, rerr := rardecode.OpenReader(e.options.Input) if rerr != nil { - fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", e.Input, rerr) + fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", e.options.Input, rerr) os.Exit(1) } defer r.Close() @@ -350,16 +350,16 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *tasks, err }() // send file to the queue - output = make(chan *tasks, e.Workers) + output = make(chan *tasks, e.options.Workers) wg := &sync.WaitGroup{} - for j := 0; j < e.WorkersRatio(50); j++ { + for j := 0; j < e.options.WorkersRatio(50); j++ { wg.Add(1) go func() { defer wg.Done() for job := range jobs { var img image.Image var err error - if !e.Dry { + if !e.options.Dry { var f io.ReadCloser f, err = job.Open() if err == nil { @@ -391,7 +391,7 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *tasks, err // extract image from a pdf func (e *EPUBImageProcessor) loadPdf() (totalImages int, output chan *tasks, err error) { - pdf := pdfread.Load(e.Input) + pdf := pdfread.Load(e.options.Input) if pdf == nil { err = fmt.Errorf("can't read pdf") return @@ -406,7 +406,7 @@ func (e *EPUBImageProcessor) loadPdf() (totalImages int, output chan *tasks, err for i := 0; i < totalImages; i++ { var img image.Image var err error - if !e.Dry { + if !e.options.Dry { img, err = pdfimage.Extract(pdf, i+1) }