Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement: rotate and split options #45

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ Kojirou can be configured to fall back on reencoded lower-quality versions of th
kojirou d86cf65b-5f6c-437d-a0af-19a31f94ec55 -l en --data-saver=fallback
```

### Rotate and split double panel pages

Kojirou has the ability to rotate and split double panel pages into two new pages.
It's possible to only rotate or rotate and split them.

Only rotating
``` shell
kojirou d86cf65b-5f6c-437d-a0af-19a31f94ec55 -l en --rotate
```

Rotating and splitting
``` shell
kojirou d86cf65b-5f6c-437d-a0af-19a31f94ec55 -l en --rotateAndSplit
```

## Prebuilt binaries

Prebuilt binaries for Linux, Windows and MacOS on x86 and ARM processors are provided.
Expand Down
87 changes: 87 additions & 0 deletions cmd/business.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package cmd

import (
"fmt"
"sort"

"github.com/leotaku/kojirou/cmd/crop"
"github.com/leotaku/kojirou/cmd/filter"
"github.com/leotaku/kojirou/cmd/formats"
"github.com/leotaku/kojirou/cmd/formats/disk"
"github.com/leotaku/kojirou/cmd/formats/download"
"github.com/leotaku/kojirou/cmd/formats/kindle"
"github.com/leotaku/kojirou/cmd/split"
md "github.com/leotaku/kojirou/mangadex"
"golang.org/x/text/language"
)
Expand Down Expand Up @@ -63,6 +65,18 @@ func handleVolume(skeleton md.Manga, volume md.Volume, dir kindle.NormalizedDire
return fmt.Errorf("autocrop: %w", err)
}
}

if rotateAndSplitArg {
if pages, err = rotateAndSplit(pages); err != nil {
return fmt.Errorf("rotateAndSplit: %w", err)
}
}

if rotateArg {
if err := rotateDoublePage(pages); err != nil {
return fmt.Errorf("rotateDoublePage: %w", err)
}
}

mangaForVolume := skeleton.WithChapters(volume.Sorted()).WithPages(pages)
mobi := kindle.GenerateMOBI(mangaForVolume)
Expand Down Expand Up @@ -212,3 +226,76 @@ func filterAndSortFromFlags(cl md.ChapterList) (md.ChapterList, error) {

return cl, nil
}

func rotateDoublePage(pages md.ImageList) error {
p := formats.VanishingProgress("Rotating..")
p.Increase(len(pages))

sort.Slice(pages, func(i, j int) bool {
return pages[i].ImageIdentifier < pages[j].ImageIdentifier
})

for i, page := range pages {
if split.IsDoublePage(page.Image) {
landscapeImage, _ := split.RotateImage(page.Image)
pages[i].Image = landscapeImage
}
p.Add(1)
}

p.Done()
return nil
}

func rotateAndSplit(pages md.ImageList) (md.ImageList, error) {
p := formats.VanishingProgress("Splitting..")
p.Increase(len(pages))

sort.Slice(pages, func(i, j int) bool {
return pages[i].ImageIdentifier < pages[j].ImageIdentifier
})

occupied := make(map[int]bool)
newPages := make(md.ImageList, len(pages))
copy(newPages, pages)

for i, page := range pages {
imgId := split.GetNextImageIdentifier(page.ImageIdentifier, occupied)
image := page.Image

if split.IsDoublePage(image) {
landscapeImage, _ := split.RotateImage(image)
newPages[i].Image = landscapeImage
newPages[i].ImageIdentifier = imgId

leftImage, rightImage, _ := split.SplitVertically(image)

rightPage := md.Image{
Image: rightImage,
ChapterIdentifier: page.ChapterIdentifier,
VolumeIdentifier: page.VolumeIdentifier,
ImageIdentifier: imgId+1,
}

leftPage := md.Image{
Image: leftImage,
ChapterIdentifier: page.ChapterIdentifier,
VolumeIdentifier: page.VolumeIdentifier,
ImageIdentifier: imgId+2,
}

newPages = append(newPages, rightPage, leftPage)
occupied[rightPage.ImageIdentifier] = true
occupied[leftPage.ImageIdentifier] = true
} else {
newPages[i].Image = image
newPages[i].ImageIdentifier = imgId
}

occupied[imgId] = true
p.Add(1)
}

p.Done()
return newPages, nil
}
4 changes: 4 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ var (
languageArg string
rankArg string
autocropArg bool
rotateAndSplitArg bool
rotateArg bool
kindleFolderModeArg bool
dryRunArg bool
outArg string
Expand Down Expand Up @@ -170,6 +172,8 @@ func init() {
rootCmd.Flags().StringVarP(&languageArg, "language", "l", "en", "language for chapter downloads")
rootCmd.Flags().StringVarP(&rankArg, "rank", "r", "most", "chapter ranking method to use")
rootCmd.Flags().BoolVarP(&autocropArg, "autocrop", "a", false, "crop whitespace from pages automatically")
rootCmd.Flags().BoolVarP(&rotateAndSplitArg, "rotateAndSplit", "S", false, "rotate and split double panels pages into two new separate pages")
rootCmd.Flags().BoolVarP(&rotateArg, "rotate", "H", false, "rotate horizontally double panels pages")
rootCmd.Flags().BoolVarP(&kindleFolderModeArg, "kindle-folder-mode", "k", false, "generate folder structure for Kindle devices")
rootCmd.Flags().BoolVarP(&leftToRightArg, "left-to-right", "p", false, "make reading direction left to right")
rootCmd.Flags().IntVarP(&fillVolumeNumberArg, "fill-volume-number", "n", 0, "fill volume number with leading zeros in title")
Expand Down
66 changes: 66 additions & 0 deletions cmd/split/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package split

import (
"fmt"
"image"
)

func IsDoublePage(img image.Image) bool {
bounds := img.Bounds()
return bounds.Dx() >= bounds.Dy()
}

func GetNextImageIdentifier(current int, occupied map[int]bool) int {
// while the current identifier is already occupied, increment it
for occupied[current] {
current++
}
return current
}

func SplitVertically(img image.Image) (image.Image, image.Image, error) {
type subImager interface {
image.Image
SubImage(r image.Rectangle) image.Image
}

subImg, ok := img.(subImager)
if !ok {
return nil, nil, fmt.Errorf("image does not support splitting or not a valid image")
}

originalBounds := subImg.Bounds()
xMiddle := originalBounds.Dx() / 2

leftBounds := image.Rectangle{
Min: image.Point{0, 0},
Max: image.Point{xMiddle, originalBounds.Dy()},
}
rightBounds := image.Rectangle{
Min: image.Point{xMiddle, 0},
Max: image.Point{originalBounds.Dx(), originalBounds.Dy()},
}

leftImage := subImg.SubImage(leftBounds)
rightImage := subImg.SubImage(rightBounds)

return leftImage, rightImage, nil
}

func RotateImage(img image.Image) (image.Image, error) {
originalBounds := img.Bounds()
width := originalBounds.Dx()
height := originalBounds.Dy()

// create a new empty image with rotated dimensions
rotatedImage := image.NewRGBA(image.Rect(0, 0, height, width))

// rotate the image by mapping each pixel from the original to the new position
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
rotatedImage.Set(y, width-1-x, img.At(x, y))
}
}

return rotatedImage, nil
}