add auto split double page

This commit is contained in:
Celogeek 2023-01-14 18:11:22 +01:00
parent 991e95f02e
commit 7adea5ddbd
Signed by: celogeek
GPG Key ID: E6B7BDCFC446233A
9 changed files with 167 additions and 76 deletions

View File

@ -80,6 +80,8 @@ Usage of go-comic-converter:
Author of the epub (default "GO Comic Converter")
-autorotate
Auto Rotate page when width > height
-autosplitdoublepage
Auto Split double page when width > height
-brightness int
Brightness readjustement: between -100 and 100, > 0 lighter, < 0 darker
-contrast int

View File

@ -21,6 +21,7 @@ type ImageOptions struct {
Brightness int
Contrast int
AutoRotate bool
AutoSplitDoublePage bool
Workers int
}
@ -184,7 +185,7 @@ func (e *ePub) Write() error {
wz.WriteImage(part.Cover.Data)
for _, img := range part.Images {
text := fmt.Sprintf("OEBPS/Text/%d.xhtml", img.Id)
text := fmt.Sprintf("OEBPS/Text/%d_p%d.xhtml", img.Id, img.Part)
if err := wz.WriteFile(text, e.render(textTmpl, img)); err != nil {
return err
}

View File

@ -0,0 +1,35 @@
package filters
import (
"image"
"image/draw"
"github.com/disintegration/gift"
)
func CropSplitDoublePage(right bool) *cropSplitDoublePage {
return &cropSplitDoublePage{right}
}
type cropSplitDoublePage struct {
right bool
}
func (p *cropSplitDoublePage) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
if p.right {
dstBounds = image.Rectangle{
Min: image.Point{srcBounds.Max.X / 2, srcBounds.Min.Y},
Max: srcBounds.Max,
}
} else {
dstBounds = image.Rectangle{
Min: srcBounds.Min,
Max: image.Point{srcBounds.Max.X / 2, srcBounds.Max.Y},
}
}
return
}
func (p *cropSplitDoublePage) Draw(dst draw.Image, src image.Image, options *gift.Options) {
gift.Crop(dst.Bounds()).Draw(dst, src, options)
}

View File

@ -4,7 +4,10 @@ import (
"archive/zip"
"bytes"
"compress/flate"
"fmt"
"hash/crc32"
"image"
"image/jpeg"
"time"
)
@ -17,13 +20,23 @@ func (img *ImageData) CompressedSize() uint64 {
return img.Header.CompressedSize64 + 30 + uint64(len(img.Header.Name))
}
func newImageData(name string, data []byte) *ImageData {
func newImageData(id int, part int, img image.Image, quality int) *ImageData {
name := fmt.Sprintf("OEBPS/Images/%d_p%d.jpg", id, part)
if id == 0 {
name = "OEBPS/Images/cover.jpg"
}
data := bytes.NewBuffer([]byte{})
if err := jpeg.Encode(data, img, &jpeg.Options{Quality: quality}); err != nil {
panic(err)
}
cdata := bytes.NewBuffer([]byte{})
wcdata, err := flate.NewWriter(cdata, flate.BestCompression)
if err != nil {
panic(err)
}
wcdata.Write(data)
wcdata.Write(data.Bytes())
wcdata.Close()
if err != nil {
panic(err)
@ -33,8 +46,8 @@ func newImageData(name string, data []byte) *ImageData {
&zip.FileHeader{
Name: name,
CompressedSize64: uint64(cdata.Len()),
UncompressedSize64: uint64(len(data)),
CRC32: crc32.Checksum(data, crc32.IEEETable),
UncompressedSize64: uint64(data.Len()),
CRC32: crc32.Checksum(data.Bytes(), crc32.IEEETable),
Method: zip.Deflate,
ModifiedTime: uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11),
ModifiedDate: uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9),

View File

@ -23,3 +23,29 @@ func NewGift(options *ImageOptions) *gift.GIFT {
)
return g
}
func NewGiftSplitDoublePage(options *ImageOptions) []*gift.GIFT {
gifts := make([]*gift.GIFT, 2)
gifts[0] = gift.New(
filters.CropSplitDoublePage(false),
)
gifts[1] = gift.New(
filters.CropSplitDoublePage(true),
)
for _, g := range gifts {
if options.Contrast != 0 {
g.Add(gift.Contrast(float32(options.Contrast)))
}
if options.Brightness != 0 {
g.Add(gift.Brightness(float32(options.Brightness)))
}
g.Add(
gift.ResizeToFit(options.ViewWidth, options.ViewHeight, gift.LanczosResampling),
)
}
return gifts
}

View File

@ -6,7 +6,6 @@ import (
"fmt"
"image"
"image/color"
"image/jpeg"
"io"
"io/fs"
"os"
@ -24,6 +23,7 @@ import (
type Image struct {
Id int
Part int
Data *ImageData
Width int
Height int
@ -154,23 +154,29 @@ func LoadImages(path string, options *ImageOptions) ([]*Image, error) {
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, b.Bytes()),
0,
newImageData(img.Id, 0, dst, options.Quality),
dst.Bounds().Dx(),
dst.Bounds().Dy(),
}
if options.AutoSplitDoublePage && src.Bounds().Dx() > src.Bounds().Dy() {
gifts := NewGiftSplitDoublePage(options)
for i, g := range gifts {
part := i + 1
dst := image.NewPaletted(g.Bounds(src.Bounds()), options.Palette)
g.Draw(dst, src)
imageOutput <- &Image{
img.Id,
part,
newImageData(img.Id, part, dst, options.Quality),
dst.Bounds().Dx(),
dst.Bounds().Dy(),
}
}
}
}
}()
}
@ -183,8 +189,10 @@ func LoadImages(path string, options *ImageOptions) ([]*Image, error) {
bar := NewBar(imageCount, "Processing", 1, 2)
for image := range imageOutput {
images = append(images, image)
if image.Part == 0 {
bar.Add(1)
}
}
bar.Close()
if len(images) == 0 {
@ -192,7 +200,12 @@ func LoadImages(path string, options *ImageOptions) ([]*Image, error) {
}
sort.Slice(images, func(i, j int) bool {
return images[i].Id < images[j].Id
if images[i].Id < images[j].Id {
return true
} else if images[i].Id == images[j].Id && images[i].Part < images[j].Part {
return true
}
return false
})
return images, nil

View File

@ -26,18 +26,14 @@
<item id="css" href="Text/style.css" media-type="text/css"/>
<item id="page_part" href="Text/part.xhtml" media-type="application/xhtml+xml"/>
{{ range .Images }}
<item id="page_{{ .Id }}" href="Text/{{ .Id }}.xhtml" media-type="application/xhtml+xml"/>
<item id="img_{{ .Id }}" href="Images/{{ .Id }}.jpg" media-type="image/jpeg"/>
<item id="page_{{ .Id }}_p{{ .Part}}" href="Text/{{ .Id }}_p{{ .Part}}.xhtml" media-type="application/xhtml+xml"/>
<item id="img_{{ .Id }}_p{{ .Part}}" href="Images/{{ .Id }}_p{{ .Part}}.jpg" media-type="image/jpeg"/>
{{ end }}
</manifest>
<spine page-progression-direction="ltr" toc="ncx">
<itemref idref="page_part" linear="yes" properties="page-spread-right"/>
{{ range $idx, $ := .Images }}
{{ if mod $idx 2 }}
<itemref idref="page_{{ $.Id }}" linear="yes" properties="page-spread-left"/>
{{ else }}
<itemref idref="page_{{ $.Id }}" linear="yes" properties="page-spread-right"/>
{{ end }}
<itemref idref="page_{{ $.Id }}_p{{ $.Part }}" linear="yes" properties="page-spread-{{ if mod $idx 2 }}left{{ else }}right{{ end }}"/>
{{ end }}
</spine>
</package>

View File

@ -2,13 +2,13 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops">
<head>
<title>Page {{ .Id }}</title>
<title>Page {{ .Id }}_p{{ .Part}}</title>
<link href="style.css" type="text/css" rel="stylesheet"/>
<meta name="viewport" content="width={{ .Width }}, height={{ .Height }}"/>
</head>
<body style="">
<div style="text-align:center;top:0.0%;">
<img width="{{ .Width }}" height="{{ .Height }}" src="../Images/{{ .Id }}.jpg"/>
<img width="{{ .Width }}" height="{{ .Height }}" src="../Images/{{ .Id }}_p{{ .Part}}.jpg"/>
</div>
<div id="PV">
<div id="PV-TL">
@ -25,16 +25,16 @@
</div>
</div>
<div class="PV-P" id="PV-TL-P" style="">
<img style="position:absolute;left:0;top:0;" src="../Images/{{ .Id }}.jpg" width="{{ zoom .Width 1.5 }}" height="{{ zoom .Height 1.5 }}"/>
<img style="position:absolute;left:0;top:0;" src="../Images/{{ .Id }}_p{{ .Part}}.jpg" width="{{ zoom .Width 1.5 }}" height="{{ zoom .Height 1.5 }}"/>
</div>
<div class="PV-P" id="PV-TR-P" style="">
<img style="position:absolute;right:0;top:0;" src="../Images/{{ .Id }}.jpg" width="{{ zoom .Width 1.5 }}" height="{{ zoom .Height 1.5 }}"/>
<img style="position:absolute;right:0;top:0;" src="../Images/{{ .Id }}_p{{ .Part}}.jpg" width="{{ zoom .Width 1.5 }}" height="{{ zoom .Height 1.5 }}"/>
</div>
<div class="PV-P" id="PV-BL-P" style="">
<img style="position:absolute;left:0;bottom:0;" src="../Images/{{ .Id }}.jpg" width="{{ zoom .Width 1.5 }}" height="{{ zoom .Height 1.5 }}"/>
<img style="position:absolute;left:0;bottom:0;" src="../Images/{{ .Id }}_p{{ .Part}}.jpg" width="{{ zoom .Width 1.5 }}" height="{{ zoom .Height 1.5 }}"/>
</div>
<div class="PV-P" id="PV-BR-P" style="">
<img style="position:absolute;right:0;bottom:0;" src="../Images/{{ .Id }}.jpg" width="{{ zoom .Width 1.5 }}" height="{{ zoom .Height 1.5 }}"/>
<img style="position:absolute;right:0;bottom:0;" src="../Images/{{ .Id }}_p{{ .Part}}.jpg" width="{{ zoom .Width 1.5 }}" height="{{ zoom .Height 1.5 }}"/>
</div>
</body>
</html>

View File

@ -67,6 +67,7 @@ type Option struct {
Brightness int
Contrast int
AutoRotate bool
AutoSplitDoublePage bool
Workers int
LimitMb int
}
@ -96,9 +97,10 @@ Options:
Title : %s
Quality : %d
Crop : %v
Brightness: %d
Brightness : %d
Contrast : %d
AutoRotate: %v
AutoRotate : %v
AutoSplitDoublePage: %v
LimitMb : %s
Workers : %d
`,
@ -112,6 +114,7 @@ Options:
o.Brightness,
o.Contrast,
o.AutoRotate,
o.AutoSplitDoublePage,
limitmb,
o.Workers,
)
@ -140,6 +143,7 @@ func main() {
flag.IntVar(&opt.Brightness, "brightness", 0, "Brightness readjustement: between -100 and 100, > 0 lighter, < 0 darker")
flag.IntVar(&opt.Contrast, "contrast", 0, "Contrast readjustement: between -100 and 100, > 0 more contrast, < 0 less contrast")
flag.BoolVar(&opt.AutoRotate, "autorotate", false, "Auto Rotate page when width > height")
flag.BoolVar(&opt.AutoSplitDoublePage, "autosplitdoublepage", false, "Auto Split double page when width > height")
flag.IntVar(&opt.LimitMb, "limitmb", 0, "Limit size of the ePub: Default nolimit (0), Minimum 20")
flag.IntVar(&opt.Workers, "workers", runtime.NumCPU(), "Number of workers")
flag.Usage = func() {
@ -239,6 +243,7 @@ func main() {
Brightness: opt.Brightness,
Contrast: opt.Contrast,
AutoRotate: opt.AutoRotate,
AutoSplitDoublePage: opt.AutoSplitDoublePage,
Workers: opt.Workers,
},
}).Write(); err != nil {