mirror of
https://github.com/celogeek/go-comic-converter.git
synced 2025-07-05 11:19:55 +02:00
Compare commits
No commits in common. "2650b926f8e27f2d48dee3ec1c9b309bbb83d423" and "003a91a07e73c4e143aaa1bda6f3971dc0966020" have entirely different histories.
2650b926f8
...
003a91a07e
@ -108,7 +108,6 @@ func (c *Converter) InitParse() {
|
|||||||
c.AddStringParam(&c.Options.Profile, "profile", c.Options.Profile, fmt.Sprintf("Profile to use: \n%s", c.Options.AvailableProfiles()))
|
c.AddStringParam(&c.Options.Profile, "profile", c.Options.Profile, fmt.Sprintf("Profile to use: \n%s", c.Options.AvailableProfiles()))
|
||||||
c.AddIntParam(&c.Options.Quality, "quality", c.Options.Quality, "Quality of the image")
|
c.AddIntParam(&c.Options.Quality, "quality", c.Options.Quality, "Quality of the image")
|
||||||
c.AddBoolParam(&c.Options.Grayscale, "grayscale", c.Options.Grayscale, "Grayscale image. Ideal for eInk devices.")
|
c.AddBoolParam(&c.Options.Grayscale, "grayscale", c.Options.Grayscale, "Grayscale image. Ideal for eInk devices.")
|
||||||
c.AddIntParam(&c.Options.GrayscaleMode, "grayscale-mode", c.Options.GrayscaleMode, "Grayscale Mode\n0 = normal\n1 = average\n2 = luminance")
|
|
||||||
c.AddBoolParam(&c.Options.Crop, "crop", c.Options.Crop, "Crop images")
|
c.AddBoolParam(&c.Options.Crop, "crop", c.Options.Crop, "Crop images")
|
||||||
c.AddIntParam(&c.Options.CropRatioLeft, "crop-ratio-left", c.Options.CropRatioLeft, "Crop ratio left: ratio of pixels allow to be non blank while cutting on the left.")
|
c.AddIntParam(&c.Options.CropRatioLeft, "crop-ratio-left", c.Options.CropRatioLeft, "Crop ratio left: ratio of pixels allow to be non blank while cutting on the left.")
|
||||||
c.AddIntParam(&c.Options.CropRatioUp, "crop-ratio-up", c.Options.CropRatioUp, "Crop ratio up: ratio of pixels allow to be non blank while cutting on the top.")
|
c.AddIntParam(&c.Options.CropRatioUp, "crop-ratio-up", c.Options.CropRatioUp, "Crop ratio up: ratio of pixels allow to be non blank while cutting on the top.")
|
||||||
@ -130,7 +129,6 @@ func (c *Converter) InitParse() {
|
|||||||
c.AddStringParam(&c.Options.Format, "format", c.Options.Format, "Format of output images: jpeg (lossy), png (lossless)")
|
c.AddStringParam(&c.Options.Format, "format", c.Options.Format, "Format of output images: jpeg (lossy), png (lossless)")
|
||||||
c.AddFloatParam(&c.Options.AspectRatio, "aspect-ratio", c.Options.AspectRatio, "Aspect ratio (height/width) of the output\n -1 = same as device\n 0 = same as source\n1.6 = amazon advice for kindle")
|
c.AddFloatParam(&c.Options.AspectRatio, "aspect-ratio", c.Options.AspectRatio, "Aspect ratio (height/width) of the output\n -1 = same as device\n 0 = same as source\n1.6 = amazon advice for kindle")
|
||||||
c.AddBoolParam(&c.Options.PortraitOnly, "portrait-only", c.Options.PortraitOnly, "Portrait only: force orientation to portrait only.")
|
c.AddBoolParam(&c.Options.PortraitOnly, "portrait-only", c.Options.PortraitOnly, "Portrait only: force orientation to portrait only.")
|
||||||
c.AddIntParam(&c.Options.TitlePage, "titlepage", c.Options.TitlePage, "Title page\n0 = never\n1 = always\n2 = only if epub is splitted")
|
|
||||||
|
|
||||||
c.AddSection("Default config")
|
c.AddSection("Default config")
|
||||||
c.AddBoolParam(&c.Options.Show, "show", false, "Show your default parameters")
|
c.AddBoolParam(&c.Options.Show, "show", false, "Show your default parameters")
|
||||||
@ -363,17 +361,7 @@ func (c *Converter) Validate() error {
|
|||||||
|
|
||||||
// Aspect Ratio
|
// Aspect Ratio
|
||||||
if c.Options.AspectRatio < 0 && c.Options.AspectRatio != -1 {
|
if c.Options.AspectRatio < 0 && c.Options.AspectRatio != -1 {
|
||||||
return errors.New("aspect ratio should be -1, 0 or > 0")
|
return errors.New("aspect ratio should be: -1, 0, > 0")
|
||||||
}
|
|
||||||
|
|
||||||
// Title Page
|
|
||||||
if c.Options.TitlePage < 0 || c.Options.TitlePage > 2 {
|
|
||||||
return errors.New("title page should be 0, 1 or 2")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grayscale Mode
|
|
||||||
if c.Options.GrayscaleMode < 0 || c.Options.GrayscaleMode > 2 {
|
|
||||||
return errors.New("grayscale mode should be 0, 1 or 2")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -24,7 +24,6 @@ type Options struct {
|
|||||||
Profile string `yaml:"profile"`
|
Profile string `yaml:"profile"`
|
||||||
Quality int `yaml:"quality"`
|
Quality int `yaml:"quality"`
|
||||||
Grayscale bool `yaml:"grayscale"`
|
Grayscale bool `yaml:"grayscale"`
|
||||||
GrayscaleMode int `yaml:"grayscale_mode"` // 0 = normal, 1 = average, 2 = luminance
|
|
||||||
Crop bool `yaml:"crop"`
|
Crop bool `yaml:"crop"`
|
||||||
CropRatioLeft int `yaml:"crop_ratio_left"`
|
CropRatioLeft int `yaml:"crop_ratio_left"`
|
||||||
CropRatioUp int `yaml:"crop_ratio_up"`
|
CropRatioUp int `yaml:"crop_ratio_up"`
|
||||||
@ -46,7 +45,6 @@ type Options struct {
|
|||||||
Format string `yaml:"format"`
|
Format string `yaml:"format"`
|
||||||
AspectRatio float64 `yaml:"aspect_ratio"`
|
AspectRatio float64 `yaml:"aspect_ratio"`
|
||||||
PortraitOnly bool `yaml:"portrait_only"`
|
PortraitOnly bool `yaml:"portrait_only"`
|
||||||
TitlePage int `yaml:"title_page"`
|
|
||||||
|
|
||||||
// Default Config
|
// Default Config
|
||||||
Show bool `yaml:"-"`
|
Show bool `yaml:"-"`
|
||||||
@ -89,7 +87,6 @@ func New() *Options {
|
|||||||
ForegroundColor: "000",
|
ForegroundColor: "000",
|
||||||
BackgroundColor: "FFF",
|
BackgroundColor: "FFF",
|
||||||
Format: "jpeg",
|
Format: "jpeg",
|
||||||
TitlePage: 1,
|
|
||||||
profiles: profiles.New(),
|
profiles: profiles.New(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,24 +167,6 @@ func (o *Options) ShowConfig() string {
|
|||||||
aspectRatio = fmt.Sprintf("1:%0.2f (device)", float64(profile.Height)/float64(profile.Width))
|
aspectRatio = fmt.Sprintf("1:%0.2f (device)", float64(profile.Height)/float64(profile.Width))
|
||||||
}
|
}
|
||||||
|
|
||||||
titlePage := ""
|
|
||||||
switch o.TitlePage {
|
|
||||||
case 0:
|
|
||||||
titlePage = "never"
|
|
||||||
case 1:
|
|
||||||
titlePage = "always"
|
|
||||||
case 2:
|
|
||||||
titlePage = "when epub is splitted"
|
|
||||||
}
|
|
||||||
|
|
||||||
grayscaleMode := "normal"
|
|
||||||
switch o.GrayscaleMode {
|
|
||||||
case 1:
|
|
||||||
grayscaleMode = "average"
|
|
||||||
case 2:
|
|
||||||
grayscaleMode = "luminance"
|
|
||||||
}
|
|
||||||
|
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
for _, v := range []struct {
|
for _, v := range []struct {
|
||||||
Key string
|
Key string
|
||||||
@ -198,7 +177,6 @@ func (o *Options) ShowConfig() string {
|
|||||||
{"Format", o.Format, true},
|
{"Format", o.Format, true},
|
||||||
{"Quality", o.Quality, o.Format == "jpeg"},
|
{"Quality", o.Quality, o.Format == "jpeg"},
|
||||||
{"Grayscale", o.Grayscale, true},
|
{"Grayscale", o.Grayscale, true},
|
||||||
{"Grayscale Mode", grayscaleMode, o.Grayscale},
|
|
||||||
{"Crop", o.Crop, true},
|
{"Crop", o.Crop, true},
|
||||||
{"CropRatio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom), o.Crop},
|
{"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},
|
{"Brightness", o.Brightness, o.Brightness != 0},
|
||||||
@ -216,7 +194,6 @@ func (o *Options) ShowConfig() string {
|
|||||||
{"Resize", !o.NoResize, true},
|
{"Resize", !o.NoResize, true},
|
||||||
{"Aspect Ratio", aspectRatio, true},
|
{"Aspect Ratio", aspectRatio, true},
|
||||||
{"Portrait Only", o.PortraitOnly, true},
|
{"Portrait Only", o.PortraitOnly, true},
|
||||||
{"Title Page", titlePage, true},
|
|
||||||
} {
|
} {
|
||||||
if v.Condition {
|
if v.Condition {
|
||||||
b.WriteString(fmt.Sprintf("\n %-26s: %v", v.Key, v.Value))
|
b.WriteString(fmt.Sprintf("\n %-26s: %v", v.Key, v.Value))
|
||||||
|
@ -99,105 +99,6 @@ func (e *ePub) writeBlank(wz *epubzip.EPUBZip, img *epubimage.Image) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// write title image
|
|
||||||
func (e *ePub) writeCoverImage(wz *epubzip.EPUBZip, img *epubimage.Image, part, totalParts int) error {
|
|
||||||
title := "Cover"
|
|
||||||
text := ""
|
|
||||||
if totalParts > 1 {
|
|
||||||
text = fmt.Sprintf("%d / %d", part, totalParts)
|
|
||||||
title = fmt.Sprintf("%s %s", title, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wz.WriteContent(
|
|
||||||
"OEBPS/Text/cover.xhtml",
|
|
||||||
[]byte(e.render(epubtemplates.Text, map[string]any{
|
|
||||||
"Title": title,
|
|
||||||
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height),
|
|
||||||
"ImagePath": fmt.Sprintf("Images/cover.%s", e.Image.Format),
|
|
||||||
"ImageStyle": img.ImgStyle(e.Image.View.Width, e.Image.View.Height, ""),
|
|
||||||
})),
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
coverTitle, err := e.imageProcessor.CoverTitleData(&epubimageprocessor.CoverTitleDataOptions{
|
|
||||||
Src: img.Raw,
|
|
||||||
Name: "cover",
|
|
||||||
Text: text,
|
|
||||||
Align: "bottom",
|
|
||||||
PctWidth: 50,
|
|
||||||
PctMargin: 50,
|
|
||||||
MaxFontSize: 96,
|
|
||||||
BorderSize: 8,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wz.WriteRaw(coverTitle); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// write title image
|
|
||||||
func (e *ePub) writeTitleImage(wz *epubzip.EPUBZip, img *epubimage.Image, title string) error {
|
|
||||||
titleAlign := ""
|
|
||||||
if !e.Image.View.PortraitOnly {
|
|
||||||
if e.Image.Manga {
|
|
||||||
titleAlign = "right:0"
|
|
||||||
} else {
|
|
||||||
titleAlign = "left:0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !e.Image.View.PortraitOnly {
|
|
||||||
if err := wz.WriteContent(
|
|
||||||
"OEBPS/Text/space_title.xhtml",
|
|
||||||
[]byte(e.render(epubtemplates.Blank, map[string]any{
|
|
||||||
"Title": "Blank Page Title",
|
|
||||||
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height),
|
|
||||||
})),
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wz.WriteContent(
|
|
||||||
"OEBPS/Text/title.xhtml",
|
|
||||||
[]byte(e.render(epubtemplates.Text, map[string]any{
|
|
||||||
"Title": title,
|
|
||||||
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height),
|
|
||||||
"ImagePath": fmt.Sprintf("Images/title.%s", e.Image.Format),
|
|
||||||
"ImageStyle": img.ImgStyle(e.Image.View.Width, e.Image.View.Height, titleAlign),
|
|
||||||
})),
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
coverTitle, err := e.imageProcessor.CoverTitleData(&epubimageprocessor.CoverTitleDataOptions{
|
|
||||||
Src: img.Raw,
|
|
||||||
Name: "title",
|
|
||||||
Text: title,
|
|
||||||
Align: "center",
|
|
||||||
PctWidth: 100,
|
|
||||||
PctMargin: 100,
|
|
||||||
MaxFontSize: 64,
|
|
||||||
BorderSize: 4,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := wz.WriteRaw(coverTitle); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract image and split it into part
|
// extract image and split it into part
|
||||||
func (e *ePub) getParts() (parts []*epubPart, imgStorage *epubzip.EPUBZipStorageImageReader, err error) {
|
func (e *ePub) getParts() (parts []*epubPart, imgStorage *epubzip.EPUBZipStorageImageReader, err error) {
|
||||||
images, err := e.imageProcessor.Load()
|
images, err := e.imageProcessor.Load()
|
||||||
@ -236,8 +137,11 @@ func (e *ePub) getParts() (parts []*epubPart, imgStorage *epubzip.EPUBZipStorage
|
|||||||
// compute size of the EPUB part and try to be as close as possible of the target
|
// compute size of the EPUB part and try to be as close as possible of the target
|
||||||
maxSize := uint64(e.LimitMb * 1024 * 1024)
|
maxSize := uint64(e.LimitMb * 1024 * 1024)
|
||||||
xhtmlSize := uint64(1024)
|
xhtmlSize := uint64(1024)
|
||||||
// descriptor files + title + cover
|
// descriptor files + title
|
||||||
baseSize := uint64(16*1024) + imgStorage.Size(cover.EPUBImgPath())*2
|
baseSize := uint64(16*1024) + imgStorage.Size(cover.EPUBImgPath())
|
||||||
|
if e.Image.HasCover {
|
||||||
|
baseSize += imgStorage.Size(cover.EPUBImgPath())
|
||||||
|
}
|
||||||
|
|
||||||
currentSize := baseSize
|
currentSize := baseSize
|
||||||
currentImages := make([]*epubimage.Image, 0)
|
currentImages := make([]*epubimage.Image, 0)
|
||||||
@ -252,6 +156,9 @@ func (e *ePub) getParts() (parts []*epubPart, imgStorage *epubzip.EPUBZipStorage
|
|||||||
})
|
})
|
||||||
part += 1
|
part += 1
|
||||||
currentSize = baseSize
|
currentSize = baseSize
|
||||||
|
if !e.Image.HasCover {
|
||||||
|
currentSize += imgStorage.Size(cover.EPUBImgPath())
|
||||||
|
}
|
||||||
currentImages = make([]*epubimage.Image, 0)
|
currentImages = make([]*epubimage.Image, 0)
|
||||||
}
|
}
|
||||||
currentSize += imgSize
|
currentSize += imgSize
|
||||||
@ -372,7 +279,6 @@ func (e *ePub) Write() error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
e.computeViewPort(epubParts)
|
e.computeViewPort(epubParts)
|
||||||
hasTitlePage := e.TitlePage == 1 || (e.TitlePage == 2 && totalParts > 1)
|
|
||||||
for i, part := range epubParts {
|
for i, part := range epubParts {
|
||||||
ext := filepath.Ext(e.Output)
|
ext := filepath.Ext(e.Output)
|
||||||
suffix := ""
|
suffix := ""
|
||||||
@ -393,13 +299,19 @@ func (e *ePub) Write() error {
|
|||||||
if totalParts > 1 {
|
if totalParts > 1 {
|
||||||
title = fmt.Sprintf("%s [%d/%d]", title, i+1, totalParts)
|
title = fmt.Sprintf("%s [%d/%d]", title, i+1, totalParts)
|
||||||
}
|
}
|
||||||
|
titleAlign := ""
|
||||||
|
if !e.Image.View.PortraitOnly {
|
||||||
|
titleAlign = "left:0"
|
||||||
|
if e.Image.Manga {
|
||||||
|
titleAlign = "right:0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
content := []zipContent{
|
content := []zipContent{
|
||||||
{"META-INF/container.xml", epubtemplates.Container},
|
{"META-INF/container.xml", epubtemplates.Container},
|
||||||
{"META-INF/com.apple.ibooks.display-options.xml", epubtemplates.AppleBooks},
|
{"META-INF/com.apple.ibooks.display-options.xml", epubtemplates.AppleBooks},
|
||||||
{"OEBPS/content.opf", epubtemplates.Content(&epubtemplates.ContentOptions{
|
{"OEBPS/content.opf", epubtemplates.Content(&epubtemplates.ContentOptions{
|
||||||
Title: title,
|
Title: title,
|
||||||
HasTitlePage: hasTitlePage,
|
|
||||||
UID: e.UID,
|
UID: e.UID,
|
||||||
Author: e.Author,
|
Author: e.Author,
|
||||||
Publisher: e.Publisher,
|
Publisher: e.Publisher,
|
||||||
@ -410,10 +322,25 @@ func (e *ePub) Write() error {
|
|||||||
Current: i + 1,
|
Current: i + 1,
|
||||||
Total: totalParts,
|
Total: totalParts,
|
||||||
})},
|
})},
|
||||||
{"OEBPS/toc.xhtml", epubtemplates.Toc(title, hasTitlePage, e.StripFirstDirectoryFromToc, part.Images)},
|
{"OEBPS/toc.xhtml", epubtemplates.Toc(title, e.StripFirstDirectoryFromToc, part.Images)},
|
||||||
{"OEBPS/Text/style.css", e.render(epubtemplates.Style, map[string]any{
|
{"OEBPS/Text/style.css", e.render(epubtemplates.Style, map[string]any{
|
||||||
"View": e.Image.View,
|
"View": e.Image.View,
|
||||||
})},
|
})},
|
||||||
|
|
||||||
|
{"OEBPS/Text/title.xhtml", e.render(epubtemplates.Text, map[string]any{
|
||||||
|
"Title": title,
|
||||||
|
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height),
|
||||||
|
"ImagePath": fmt.Sprintf("Images/title.%s", e.Image.Format),
|
||||||
|
"ImageStyle": part.Cover.ImgStyle(e.Image.View.Width, e.Image.View.Height, titleAlign),
|
||||||
|
})},
|
||||||
|
}
|
||||||
|
if !e.Image.View.PortraitOnly {
|
||||||
|
content = append(content, zipContent{
|
||||||
|
"OEBPS/Text/space_title.xhtml", e.render(epubtemplates.Blank, map[string]any{
|
||||||
|
"Title": "Blank Page Title",
|
||||||
|
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height),
|
||||||
|
}),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = wz.WriteMagic(); err != nil {
|
if err = wz.WriteMagic(); err != nil {
|
||||||
@ -424,13 +351,19 @@ func (e *ePub) Write() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
coverTitle, err := e.imageProcessor.CoverTitleData(part.Cover.Raw, title)
|
||||||
if err = e.writeCoverImage(wz, part.Cover, i+1, totalParts); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasTitlePage {
|
if err := wz.WriteRaw(coverTitle); err != nil {
|
||||||
if err = e.writeTitleImage(wz, part.Cover, title); err != nil {
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cover exist or part > 1
|
||||||
|
// If no cover, part 2 and more will include the image as a cover
|
||||||
|
if e.Image.HasCover || i > 0 {
|
||||||
|
if err := e.writeImage(wz, part.Cover, imgStorage.Get(part.Cover.EPUBImgPath())); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,17 +12,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Create a title with the cover image
|
// Create a title with the cover image
|
||||||
func CoverTitle(title string, align string, pctWidth int, pctMargin int, maxFontSize int, borderSize int) gift.Filter {
|
func CoverTitle(title string) gift.Filter {
|
||||||
return &coverTitle{title, align, pctWidth, pctMargin, maxFontSize, borderSize}
|
return &coverTitle{title}
|
||||||
}
|
}
|
||||||
|
|
||||||
type coverTitle struct {
|
type coverTitle struct {
|
||||||
title string
|
title string
|
||||||
align string
|
|
||||||
pctWidth int
|
|
||||||
pctMargin int
|
|
||||||
maxFontSize int
|
|
||||||
borderSize int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// size is the same as source
|
// size is the same as source
|
||||||
@ -33,36 +28,28 @@ func (p *coverTitle) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangl
|
|||||||
// blur the src image, and create a box with the title in the middle
|
// blur the src image, and create a box with the title in the middle
|
||||||
func (p *coverTitle) Draw(dst draw.Image, src image.Image, options *gift.Options) {
|
func (p *coverTitle) Draw(dst draw.Image, src image.Image, options *gift.Options) {
|
||||||
draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src)
|
draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src)
|
||||||
if p.title == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
srcWidth, srcHeight := src.Bounds().Dx(), src.Bounds().Dy()
|
srcWidth, srcHeight := src.Bounds().Dx(), src.Bounds().Dy()
|
||||||
|
|
||||||
// Calculate size of title
|
// Calculate size of title
|
||||||
f, _ := truetype.Parse(gomonobold.TTF)
|
f, _ := truetype.Parse(gomonobold.TTF)
|
||||||
|
borderSize := 4
|
||||||
var fontSize, textWidth, textHeight int
|
var fontSize, textWidth, textHeight int
|
||||||
for fontSize = p.maxFontSize; fontSize >= 12; fontSize -= 1 {
|
for fontSize = 64; fontSize >= 12; fontSize -= 1 {
|
||||||
face := truetype.NewFace(f, &truetype.Options{Size: float64(fontSize), DPI: 72})
|
face := truetype.NewFace(f, &truetype.Options{Size: float64(fontSize), DPI: 72})
|
||||||
textWidth = font.MeasureString(face, p.title).Ceil()
|
textWidth = font.MeasureString(face, p.title).Ceil()
|
||||||
textHeight = face.Metrics().Ascent.Ceil() + face.Metrics().Descent.Ceil()
|
textHeight = face.Metrics().Ascent.Ceil() + face.Metrics().Descent.Ceil()
|
||||||
if textWidth+2*p.borderSize < srcWidth*p.pctWidth/100 && 3*textHeight+2*p.borderSize < srcHeight {
|
if textWidth+2*borderSize < srcWidth && 3*textHeight+2*borderSize < srcHeight {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw rectangle in the middle of the image
|
// Draw rectangle in the middle of the image
|
||||||
marginSize := fontSize * p.pctMargin / 100
|
textPosStart := srcHeight/2 - textHeight/2
|
||||||
var textPosStart, textPosEnd int
|
textPosEnd := srcHeight/2 + textHeight/2
|
||||||
if p.align == "bottom" {
|
marginSize := fontSize
|
||||||
textPosStart = srcHeight - textHeight - p.borderSize - marginSize
|
borderArea := image.Rect(0, textPosStart-borderSize-marginSize, srcWidth, textPosEnd+borderSize+marginSize)
|
||||||
textPosEnd = srcHeight - p.borderSize - marginSize
|
textArea := image.Rect(borderSize, textPosStart-marginSize, srcWidth-borderSize, textPosEnd+marginSize)
|
||||||
} else {
|
|
||||||
textPosStart = srcHeight/2 - textHeight/2
|
|
||||||
textPosEnd = srcHeight/2 + textHeight/2
|
|
||||||
}
|
|
||||||
borderArea := image.Rect((srcWidth-(srcWidth*p.pctWidth/100))/2, textPosStart-p.borderSize-marginSize, (srcWidth+(srcWidth*p.pctWidth/100))/2, textPosEnd+p.borderSize+marginSize)
|
|
||||||
textArea := image.Rect(borderArea.Bounds().Min.X+p.borderSize, textPosStart-marginSize, borderArea.Bounds().Max.X-p.borderSize, textPosEnd+marginSize)
|
|
||||||
|
|
||||||
draw.Draw(
|
draw.Draw(
|
||||||
dst,
|
dst,
|
||||||
@ -89,10 +76,9 @@ func (p *coverTitle) Draw(dst draw.Image, src image.Image, options *gift.Options
|
|||||||
c.SetDst(dst)
|
c.SetDst(dst)
|
||||||
c.SetSrc(image.Black)
|
c.SetSrc(image.Black)
|
||||||
|
|
||||||
textLeft := textArea.Min.X + textArea.Dx()/2 - textWidth/2
|
textLeft := srcWidth/2 - textWidth/2
|
||||||
if textLeft < textArea.Min.X {
|
if textLeft < borderSize {
|
||||||
textLeft = textArea.Min.X
|
textLeft = borderSize
|
||||||
}
|
}
|
||||||
textTop := textArea.Min.Y + textArea.Dy()/2 + textHeight/4
|
c.DrawString(p.title, freetype.Pt(textLeft, srcHeight/2+textHeight/4))
|
||||||
c.DrawString(p.title, freetype.Pt(textLeft, textTop))
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ package epubimageprocessor
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
|
||||||
"image/draw"
|
"image/draw"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
@ -169,7 +168,7 @@ func (e *EPUBImageProcessor) createImage(src image.Image, r image.Rectangle) dra
|
|||||||
// transform image into 1 or 3 images
|
// transform image into 1 or 3 images
|
||||||
// only doublepage with autosplit has 3 versions
|
// only doublepage with autosplit has 3 versions
|
||||||
func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.Image {
|
func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.Image {
|
||||||
var filters, splitFilters []gift.Filter
|
var filters, splitFilter []gift.Filter
|
||||||
var images []image.Image
|
var images []image.Image
|
||||||
|
|
||||||
// Lookup for margin if crop is enable or if we want to remove blank image
|
// Lookup for margin if crop is enable or if we want to remove blank image
|
||||||
@ -189,7 +188,7 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
|
|||||||
// crop is enable or if blank image with noblankimage options
|
// crop is enable or if blank image with noblankimage options
|
||||||
if e.Image.Crop.Enabled || (e.Image.NoBlankImage && isBlank) {
|
if e.Image.Crop.Enabled || (e.Image.NoBlankImage && isBlank) {
|
||||||
filters = append(filters, f)
|
filters = append(filters, f)
|
||||||
splitFilters = append(splitFilters, f)
|
splitFilter = append(splitFilter, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,13 +199,13 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
|
|||||||
if e.Image.Contrast != 0 {
|
if e.Image.Contrast != 0 {
|
||||||
f := gift.Contrast(float32(e.Image.Contrast))
|
f := gift.Contrast(float32(e.Image.Contrast))
|
||||||
filters = append(filters, f)
|
filters = append(filters, f)
|
||||||
splitFilters = append(splitFilters, f)
|
splitFilter = append(splitFilter, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Image.Brightness != 0 {
|
if e.Image.Brightness != 0 {
|
||||||
f := gift.Brightness(float32(e.Image.Brightness))
|
f := gift.Brightness(float32(e.Image.Brightness))
|
||||||
filters = append(filters, f)
|
filters = append(filters, f)
|
||||||
splitFilters = append(splitFilters, f)
|
splitFilter = append(splitFilter, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Image.Resize {
|
if e.Image.Resize {
|
||||||
@ -214,26 +213,6 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
|
|||||||
filters = append(filters, f)
|
filters = append(filters, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
if e.Image.GrayScale {
|
|
||||||
var f gift.Filter
|
|
||||||
switch e.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
|
|
||||||
return y, y, y, a0
|
|
||||||
})
|
|
||||||
case 2: // luminance
|
|
||||||
f = gift.ColorFunc(func(r0, g0, b0, a0 float32) (r float32, g float32, b float32, a float32) {
|
|
||||||
y := 0.2126*r0 + 0.7152*g0 + 0.0722*b0
|
|
||||||
return y, y, y, a0
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
f = gift.Grayscale()
|
|
||||||
}
|
|
||||||
filters = append(filters, f)
|
|
||||||
splitFilters = append(splitFilters, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
filters = append(filters, epubimagefilters.Pixel())
|
filters = append(filters, epubimagefilters.Pixel())
|
||||||
|
|
||||||
// convert
|
// convert
|
||||||
@ -261,7 +240,7 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
|
|||||||
|
|
||||||
// convert double page
|
// convert double page
|
||||||
for _, b := range []bool{e.Image.Manga, !e.Image.Manga} {
|
for _, b := range []bool{e.Image.Manga, !e.Image.Manga} {
|
||||||
g := gift.New(splitFilters...)
|
g := gift.New(splitFilter...)
|
||||||
g.Add(epubimagefilters.CropSplitDoublePage(b))
|
g.Add(epubimagefilters.CropSplitDoublePage(b))
|
||||||
if e.Image.Resize {
|
if e.Image.Resize {
|
||||||
g.Add(gift.ResizeToFit(e.Image.View.Width, e.Image.View.Height, gift.LanczosResampling))
|
g.Add(gift.ResizeToFit(e.Image.View.Width, e.Image.View.Height, gift.LanczosResampling))
|
||||||
@ -274,52 +253,15 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
|
|||||||
return images
|
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
|
// create a title page with the cover
|
||||||
func (e *EPUBImageProcessor) CoverTitleData(o *CoverTitleDataOptions) (*epubzip.ZipImage, error) {
|
func (e *EPUBImageProcessor) CoverTitleData(src image.Image, title string) (*epubzip.ZipImage, error) {
|
||||||
// Create a blur version of the cover
|
// Create a blur version of the cover
|
||||||
g := gift.New(epubimagefilters.CoverTitle(o.Text, o.Align, o.PctWidth, o.PctMargin, o.MaxFontSize, o.BorderSize))
|
g := gift.New(epubimagefilters.CoverTitle(title))
|
||||||
var dst draw.Image
|
dst := e.createImage(src, g.Bounds(src.Bounds()))
|
||||||
if o.Name == "cover" && e.Image.GrayScale {
|
g.Draw(dst, src)
|
||||||
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(
|
return epubzip.CompressImage(
|
||||||
fmt.Sprintf("OEBPS/Images/%s.%s", o.Name, e.Image.Format),
|
fmt.Sprintf("OEBPS/Images/title.%s", e.Image.Format),
|
||||||
e.Image.Format,
|
e.Image.Format,
|
||||||
dst,
|
dst,
|
||||||
e.Image.Quality,
|
e.Image.Quality,
|
||||||
|
@ -33,7 +33,6 @@ type Image struct {
|
|||||||
HasCover bool
|
HasCover bool
|
||||||
View *View
|
View *View
|
||||||
GrayScale bool
|
GrayScale bool
|
||||||
GrayScaleMode int
|
|
||||||
Resize bool
|
Resize bool
|
||||||
Format string
|
Format string
|
||||||
}
|
}
|
||||||
@ -42,7 +41,6 @@ type Options struct {
|
|||||||
Input string
|
Input string
|
||||||
Output string
|
Output string
|
||||||
Title string
|
Title string
|
||||||
TitlePage int
|
|
||||||
Author string
|
Author string
|
||||||
LimitMb int
|
LimitMb int
|
||||||
StripFirstDirectoryFromToc bool
|
StripFirstDirectoryFromToc bool
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
type ContentOptions struct {
|
type ContentOptions struct {
|
||||||
Title string
|
Title string
|
||||||
HasTitlePage bool
|
|
||||||
UID string
|
UID string
|
||||||
Author string
|
Author string
|
||||||
Publisher string
|
Publisher string
|
||||||
@ -125,7 +124,9 @@ func getMeta(o *ContentOptions) []tag {
|
|||||||
metas = append(metas, tag{"meta", tagAttrs{"name": "primary-writing-mode", "content": "horizontal-lr"}, ""})
|
metas = append(metas, tag{"meta", tagAttrs{"name": "primary-writing-mode", "content": "horizontal-lr"}, ""})
|
||||||
}
|
}
|
||||||
|
|
||||||
metas = append(metas, tag{"meta", tagAttrs{"name": "cover", "content": "img_cover"}, ""})
|
if o.Cover != nil {
|
||||||
|
metas = append(metas, tag{"meta", tagAttrs{"name": "cover", "content": o.Cover.ImgKey()}, ""})
|
||||||
|
}
|
||||||
|
|
||||||
if o.Total > 1 {
|
if o.Total > 1 {
|
||||||
metas = append(
|
metas = append(
|
||||||
@ -157,19 +158,16 @@ func getManifest(o *ContentOptions) []tag {
|
|||||||
items := []tag{
|
items := []tag{
|
||||||
{"item", tagAttrs{"id": "toc", "href": "toc.xhtml", "properties": "nav", "media-type": "application/xhtml+xml"}, ""},
|
{"item", tagAttrs{"id": "toc", "href": "toc.xhtml", "properties": "nav", "media-type": "application/xhtml+xml"}, ""},
|
||||||
{"item", tagAttrs{"id": "css", "href": "Text/style.css", "media-type": "text/css"}, ""},
|
{"item", tagAttrs{"id": "css", "href": "Text/style.css", "media-type": "text/css"}, ""},
|
||||||
{"item", tagAttrs{"id": "page_cover", "href": "Text/cover.xhtml", "media-type": "application/xhtml+xml"}, ""},
|
{"item", tagAttrs{"id": "page_title", "href": "Text/title.xhtml", "media-type": "application/xhtml+xml"}, ""},
|
||||||
{"item", tagAttrs{"id": "img_cover", "href": fmt.Sprintf("Images/cover.%s", o.ImageOptions.Format), "media-type": fmt.Sprintf("image/%s", o.ImageOptions.Format)}, ""},
|
{"item", tagAttrs{"id": "img_title", "href": fmt.Sprintf("Images/title.%s", o.ImageOptions.Format), "media-type": fmt.Sprintf("image/%s", o.ImageOptions.Format)}, ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.HasTitlePage {
|
if !o.ImageOptions.View.PortraitOnly {
|
||||||
items = append(items,
|
items = append(items, tag{"item", tagAttrs{"id": "space_title", "href": "Text/space_title.xhtml", "media-type": "application/xhtml+xml"}, ""})
|
||||||
tag{"item", tagAttrs{"id": "page_title", "href": "Text/title.xhtml", "media-type": "application/xhtml+xml"}, ""},
|
}
|
||||||
tag{"item", tagAttrs{"id": "img_title", "href": fmt.Sprintf("Images/title.%s", o.ImageOptions.Format), "media-type": fmt.Sprintf("image/%s", o.ImageOptions.Format)}, ""},
|
|
||||||
)
|
|
||||||
|
|
||||||
if !o.ImageOptions.View.PortraitOnly {
|
if o.ImageOptions.HasCover || o.Current > 1 {
|
||||||
items = append(items, tag{"item", tagAttrs{"id": "space_title", "href": "Text/space_title.xhtml", "media-type": "application/xhtml+xml"}, ""})
|
addTag(o.Cover, false)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastImage := o.Images[len(o.Images)-1]
|
lastImage := o.Images[len(o.Images)-1]
|
||||||
@ -204,12 +202,9 @@ func getSpineAuto(o *ContentOptions) []tag {
|
|||||||
return fmt.Sprintf("%s layout-blank", getSpread(false))
|
return fmt.Sprintf("%s layout-blank", getSpread(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
spine := []tag{}
|
spine := []tag{
|
||||||
if o.HasTitlePage {
|
{"itemref", tagAttrs{"idref": "space_title", "properties": getSpreadBlank()}, ""},
|
||||||
spine = append(spine,
|
{"itemref", tagAttrs{"idref": "page_title", "properties": getSpread(false)}, ""},
|
||||||
tag{"itemref", tagAttrs{"idref": "space_title", "properties": getSpreadBlank()}, ""},
|
|
||||||
tag{"itemref", tagAttrs{"idref": "page_title", "properties": getSpread(false)}, ""},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
for _, img := range o.Images {
|
for _, img := range o.Images {
|
||||||
if img.DoublePage && o.ImageOptions.Manga == isOnTheRight {
|
if img.DoublePage && o.ImageOptions.Manga == isOnTheRight {
|
||||||
@ -239,11 +234,8 @@ func getSpineAuto(o *ContentOptions) []tag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getSpinePortrait(o *ContentOptions) []tag {
|
func getSpinePortrait(o *ContentOptions) []tag {
|
||||||
spine := []tag{}
|
spine := []tag{
|
||||||
if o.HasTitlePage {
|
{"itemref", tagAttrs{"idref": "page_title"}, ""},
|
||||||
spine = append(spine,
|
|
||||||
tag{"itemref", tagAttrs{"idref": "page_title"}, ""},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
for _, img := range o.Images {
|
for _, img := range o.Images {
|
||||||
spine = append(spine, tag{
|
spine = append(spine, tag{
|
||||||
@ -257,8 +249,10 @@ func getSpinePortrait(o *ContentOptions) []tag {
|
|||||||
|
|
||||||
// guide part of the content
|
// guide part of the content
|
||||||
func getGuide(o *ContentOptions) []tag {
|
func getGuide(o *ContentOptions) []tag {
|
||||||
return []tag{
|
guide := []tag{}
|
||||||
{"reference", tagAttrs{"type": "cover", "title": "cover", "href": "Text/cover.xhtml"}, ""},
|
if o.Cover != nil {
|
||||||
{"reference", tagAttrs{"type": "text", "title": "content", "href": o.Images[0].PagePath()}, ""},
|
guide = append(guide, tag{"reference", tagAttrs{"type": "cover", "title": "cover", "href": o.Cover.PagePath()}, ""})
|
||||||
}
|
}
|
||||||
|
guide = append(guide, tag{"reference", tagAttrs{"type": "text", "title": "content", "href": o.Images[0].PagePath()}, ""})
|
||||||
|
return guide
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// create toc
|
// create toc
|
||||||
func Toc(title string, hasTitle bool, stripFirstDirectoryFromToc bool, images []*epubimage.Image) string {
|
func Toc(title string, stripFirstDirectoryFromToc bool, images []*epubimage.Image) string {
|
||||||
doc := etree.NewDocument()
|
doc := etree.NewDocument()
|
||||||
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
|
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
|
||||||
doc.CreateDirective("DOCTYPE html")
|
doc.CreateDirective("DOCTYPE html")
|
||||||
@ -55,11 +55,7 @@ func Toc(title string, hasTitle bool, stripFirstDirectoryFromToc bool, images []
|
|||||||
|
|
||||||
beginning := etree.NewElement("li")
|
beginning := etree.NewElement("li")
|
||||||
beginningLink := beginning.CreateElement("a")
|
beginningLink := beginning.CreateElement("a")
|
||||||
if hasTitle {
|
beginningLink.CreateAttr("href", "Text/title.xhtml")
|
||||||
beginningLink.CreateAttr("href", "Text/title.xhtml")
|
|
||||||
} else {
|
|
||||||
beginningLink.CreateAttr("href", images[0].PagePath())
|
|
||||||
}
|
|
||||||
beginningLink.CreateText(title)
|
beginningLink.CreateText(title)
|
||||||
ol.InsertChildAt(0, beginning)
|
ol.InsertChildAt(0, beginning)
|
||||||
|
|
||||||
|
6
main.go
6
main.go
@ -105,7 +105,6 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
|
|||||||
Output: cmd.Options.Output,
|
Output: cmd.Options.Output,
|
||||||
LimitMb: cmd.Options.LimitMb,
|
LimitMb: cmd.Options.LimitMb,
|
||||||
Title: cmd.Options.Title,
|
Title: cmd.Options.Title,
|
||||||
TitlePage: cmd.Options.TitlePage,
|
|
||||||
Author: cmd.Options.Author,
|
Author: cmd.Options.Author,
|
||||||
StripFirstDirectoryFromToc: cmd.Options.StripFirstDirectoryFromToc,
|
StripFirstDirectoryFromToc: cmd.Options.StripFirstDirectoryFromToc,
|
||||||
SortPathMode: cmd.Options.SortPathMode,
|
SortPathMode: cmd.Options.SortPathMode,
|
||||||
@ -114,9 +113,8 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
|
|||||||
DryVerbose: cmd.Options.DryVerbose,
|
DryVerbose: cmd.Options.DryVerbose,
|
||||||
Quiet: cmd.Options.Quiet,
|
Quiet: cmd.Options.Quiet,
|
||||||
Image: &epuboptions.Image{
|
Image: &epuboptions.Image{
|
||||||
Quality: cmd.Options.Quality,
|
Quality: cmd.Options.Quality,
|
||||||
GrayScale: cmd.Options.Grayscale,
|
GrayScale: cmd.Options.Grayscale,
|
||||||
GrayScaleMode: cmd.Options.GrayscaleMode,
|
|
||||||
Crop: &epuboptions.Crop{
|
Crop: &epuboptions.Crop{
|
||||||
Enabled: cmd.Options.Crop,
|
Enabled: cmd.Options.Crop,
|
||||||
Left: cmd.Options.CropRatioLeft,
|
Left: cmd.Options.CropRatioLeft,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user