mirror of
https://github.com/celogeek/go-comic-converter.git
synced 2025-07-04 18:59:56 +02:00
Compare commits
4 Commits
003a91a07e
...
2650b926f8
Author | SHA1 | Date | |
---|---|---|---|
2650b926f8 | |||
a54e093844 | |||
0de6cc9acd | |||
4a1ae516ea |
@ -108,6 +108,7 @@ func (c *Converter) InitParse() {
|
||||
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.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.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.")
|
||||
@ -129,6 +130,7 @@ func (c *Converter) InitParse() {
|
||||
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.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.AddBoolParam(&c.Options.Show, "show", false, "Show your default parameters")
|
||||
@ -361,7 +363,17 @@ func (c *Converter) Validate() error {
|
||||
|
||||
// Aspect Ratio
|
||||
if c.Options.AspectRatio < 0 && c.Options.AspectRatio != -1 {
|
||||
return errors.New("aspect ratio should be: -1, 0, > 0")
|
||||
return errors.New("aspect ratio should be -1, 0 or > 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
|
||||
|
@ -24,6 +24,7 @@ type Options struct {
|
||||
Profile string `yaml:"profile"`
|
||||
Quality int `yaml:"quality"`
|
||||
Grayscale bool `yaml:"grayscale"`
|
||||
GrayscaleMode int `yaml:"grayscale_mode"` // 0 = normal, 1 = average, 2 = luminance
|
||||
Crop bool `yaml:"crop"`
|
||||
CropRatioLeft int `yaml:"crop_ratio_left"`
|
||||
CropRatioUp int `yaml:"crop_ratio_up"`
|
||||
@ -45,6 +46,7 @@ type Options struct {
|
||||
Format string `yaml:"format"`
|
||||
AspectRatio float64 `yaml:"aspect_ratio"`
|
||||
PortraitOnly bool `yaml:"portrait_only"`
|
||||
TitlePage int `yaml:"title_page"`
|
||||
|
||||
// Default Config
|
||||
Show bool `yaml:"-"`
|
||||
@ -87,6 +89,7 @@ func New() *Options {
|
||||
ForegroundColor: "000",
|
||||
BackgroundColor: "FFF",
|
||||
Format: "jpeg",
|
||||
TitlePage: 1,
|
||||
profiles: profiles.New(),
|
||||
}
|
||||
}
|
||||
@ -167,6 +170,24 @@ func (o *Options) ShowConfig() string {
|
||||
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
|
||||
for _, v := range []struct {
|
||||
Key string
|
||||
@ -177,6 +198,7 @@ func (o *Options) ShowConfig() string {
|
||||
{"Format", o.Format, true},
|
||||
{"Quality", o.Quality, o.Format == "jpeg"},
|
||||
{"Grayscale", o.Grayscale, true},
|
||||
{"Grayscale Mode", grayscaleMode, o.Grayscale},
|
||||
{"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},
|
||||
{"Brightness", o.Brightness, o.Brightness != 0},
|
||||
@ -194,6 +216,7 @@ func (o *Options) ShowConfig() string {
|
||||
{"Resize", !o.NoResize, true},
|
||||
{"Aspect Ratio", aspectRatio, true},
|
||||
{"Portrait Only", o.PortraitOnly, true},
|
||||
{"Title Page", titlePage, true},
|
||||
} {
|
||||
if v.Condition {
|
||||
b.WriteString(fmt.Sprintf("\n %-26s: %v", v.Key, v.Value))
|
||||
|
@ -99,6 +99,105 @@ 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
|
||||
func (e *ePub) getParts() (parts []*epubPart, imgStorage *epubzip.EPUBZipStorageImageReader, err error) {
|
||||
images, err := e.imageProcessor.Load()
|
||||
@ -137,11 +236,8 @@ 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
|
||||
maxSize := uint64(e.LimitMb * 1024 * 1024)
|
||||
xhtmlSize := uint64(1024)
|
||||
// descriptor files + title
|
||||
baseSize := uint64(16*1024) + imgStorage.Size(cover.EPUBImgPath())
|
||||
if e.Image.HasCover {
|
||||
baseSize += imgStorage.Size(cover.EPUBImgPath())
|
||||
}
|
||||
// descriptor files + title + cover
|
||||
baseSize := uint64(16*1024) + imgStorage.Size(cover.EPUBImgPath())*2
|
||||
|
||||
currentSize := baseSize
|
||||
currentImages := make([]*epubimage.Image, 0)
|
||||
@ -156,9 +252,6 @@ func (e *ePub) getParts() (parts []*epubPart, imgStorage *epubzip.EPUBZipStorage
|
||||
})
|
||||
part += 1
|
||||
currentSize = baseSize
|
||||
if !e.Image.HasCover {
|
||||
currentSize += imgStorage.Size(cover.EPUBImgPath())
|
||||
}
|
||||
currentImages = make([]*epubimage.Image, 0)
|
||||
}
|
||||
currentSize += imgSize
|
||||
@ -279,6 +372,7 @@ func (e *ePub) Write() error {
|
||||
})
|
||||
|
||||
e.computeViewPort(epubParts)
|
||||
hasTitlePage := e.TitlePage == 1 || (e.TitlePage == 2 && totalParts > 1)
|
||||
for i, part := range epubParts {
|
||||
ext := filepath.Ext(e.Output)
|
||||
suffix := ""
|
||||
@ -299,19 +393,13 @@ func (e *ePub) Write() error {
|
||||
if totalParts > 1 {
|
||||
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{
|
||||
{"META-INF/container.xml", epubtemplates.Container},
|
||||
{"META-INF/com.apple.ibooks.display-options.xml", epubtemplates.AppleBooks},
|
||||
{"OEBPS/content.opf", epubtemplates.Content(&epubtemplates.ContentOptions{
|
||||
Title: title,
|
||||
HasTitlePage: hasTitlePage,
|
||||
UID: e.UID,
|
||||
Author: e.Author,
|
||||
Publisher: e.Publisher,
|
||||
@ -322,25 +410,10 @@ func (e *ePub) Write() error {
|
||||
Current: i + 1,
|
||||
Total: totalParts,
|
||||
})},
|
||||
{"OEBPS/toc.xhtml", epubtemplates.Toc(title, e.StripFirstDirectoryFromToc, part.Images)},
|
||||
{"OEBPS/toc.xhtml", epubtemplates.Toc(title, hasTitlePage, e.StripFirstDirectoryFromToc, part.Images)},
|
||||
{"OEBPS/Text/style.css", e.render(epubtemplates.Style, map[string]any{
|
||||
"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 {
|
||||
@ -351,19 +424,13 @@ func (e *ePub) Write() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
coverTitle, err := e.imageProcessor.CoverTitleData(part.Cover.Raw, title)
|
||||
if err != nil {
|
||||
|
||||
if err = e.writeCoverImage(wz, part.Cover, i+1, totalParts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := wz.WriteRaw(coverTitle); 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 {
|
||||
if hasTitlePage {
|
||||
if err = e.writeTitleImage(wz, part.Cover, title); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -12,12 +12,17 @@ import (
|
||||
)
|
||||
|
||||
// Create a title with the cover image
|
||||
func CoverTitle(title string) gift.Filter {
|
||||
return &coverTitle{title}
|
||||
func CoverTitle(title string, align string, pctWidth int, pctMargin int, maxFontSize int, borderSize int) gift.Filter {
|
||||
return &coverTitle{title, align, pctWidth, pctMargin, maxFontSize, borderSize}
|
||||
}
|
||||
|
||||
type coverTitle struct {
|
||||
title string
|
||||
title string
|
||||
align string
|
||||
pctWidth int
|
||||
pctMargin int
|
||||
maxFontSize int
|
||||
borderSize int
|
||||
}
|
||||
|
||||
// size is the same as source
|
||||
@ -28,28 +33,36 @@ func (p *coverTitle) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangl
|
||||
// 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) {
|
||||
draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src)
|
||||
if p.title == "" {
|
||||
return
|
||||
}
|
||||
|
||||
srcWidth, srcHeight := src.Bounds().Dx(), src.Bounds().Dy()
|
||||
|
||||
// Calculate size of title
|
||||
f, _ := truetype.Parse(gomonobold.TTF)
|
||||
borderSize := 4
|
||||
var fontSize, textWidth, textHeight int
|
||||
for fontSize = 64; fontSize >= 12; fontSize -= 1 {
|
||||
for fontSize = p.maxFontSize; fontSize >= 12; fontSize -= 1 {
|
||||
face := truetype.NewFace(f, &truetype.Options{Size: float64(fontSize), DPI: 72})
|
||||
textWidth = font.MeasureString(face, p.title).Ceil()
|
||||
textHeight = face.Metrics().Ascent.Ceil() + face.Metrics().Descent.Ceil()
|
||||
if textWidth+2*borderSize < srcWidth && 3*textHeight+2*borderSize < srcHeight {
|
||||
if textWidth+2*p.borderSize < srcWidth*p.pctWidth/100 && 3*textHeight+2*p.borderSize < srcHeight {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Draw rectangle in the middle of the image
|
||||
textPosStart := srcHeight/2 - textHeight/2
|
||||
textPosEnd := srcHeight/2 + textHeight/2
|
||||
marginSize := fontSize
|
||||
borderArea := image.Rect(0, textPosStart-borderSize-marginSize, srcWidth, textPosEnd+borderSize+marginSize)
|
||||
textArea := image.Rect(borderSize, textPosStart-marginSize, srcWidth-borderSize, textPosEnd+marginSize)
|
||||
marginSize := fontSize * p.pctMargin / 100
|
||||
var textPosStart, textPosEnd int
|
||||
if p.align == "bottom" {
|
||||
textPosStart = srcHeight - textHeight - p.borderSize - marginSize
|
||||
textPosEnd = srcHeight - p.borderSize - 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(
|
||||
dst,
|
||||
@ -76,9 +89,10 @@ func (p *coverTitle) Draw(dst draw.Image, src image.Image, options *gift.Options
|
||||
c.SetDst(dst)
|
||||
c.SetSrc(image.Black)
|
||||
|
||||
textLeft := srcWidth/2 - textWidth/2
|
||||
if textLeft < borderSize {
|
||||
textLeft = borderSize
|
||||
textLeft := textArea.Min.X + textArea.Dx()/2 - textWidth/2
|
||||
if textLeft < textArea.Min.X {
|
||||
textLeft = textArea.Min.X
|
||||
}
|
||||
c.DrawString(p.title, freetype.Pt(textLeft, srcHeight/2+textHeight/4))
|
||||
textTop := textArea.Min.Y + textArea.Dy()/2 + textHeight/4
|
||||
c.DrawString(p.title, freetype.Pt(textLeft, textTop))
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package epubimageprocessor
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"os"
|
||||
"sync"
|
||||
@ -168,7 +169,7 @@ func (e *EPUBImageProcessor) createImage(src image.Image, r image.Rectangle) dra
|
||||
// transform image into 1 or 3 images
|
||||
// only doublepage with autosplit has 3 versions
|
||||
func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.Image {
|
||||
var filters, splitFilter []gift.Filter
|
||||
var filters, splitFilters []gift.Filter
|
||||
var images []image.Image
|
||||
|
||||
// Lookup for margin if crop is enable or if we want to remove blank image
|
||||
@ -188,7 +189,7 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
|
||||
// crop is enable or if blank image with noblankimage options
|
||||
if e.Image.Crop.Enabled || (e.Image.NoBlankImage && isBlank) {
|
||||
filters = append(filters, f)
|
||||
splitFilter = append(splitFilter, f)
|
||||
splitFilters = append(splitFilters, f)
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,13 +200,13 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
|
||||
if e.Image.Contrast != 0 {
|
||||
f := gift.Contrast(float32(e.Image.Contrast))
|
||||
filters = append(filters, f)
|
||||
splitFilter = append(splitFilter, f)
|
||||
splitFilters = append(splitFilters, f)
|
||||
}
|
||||
|
||||
if e.Image.Brightness != 0 {
|
||||
f := gift.Brightness(float32(e.Image.Brightness))
|
||||
filters = append(filters, f)
|
||||
splitFilter = append(splitFilter, f)
|
||||
splitFilters = append(splitFilters, f)
|
||||
}
|
||||
|
||||
if e.Image.Resize {
|
||||
@ -213,6 +214,26 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
|
||||
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())
|
||||
|
||||
// convert
|
||||
@ -240,7 +261,7 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
|
||||
|
||||
// convert double page
|
||||
for _, b := range []bool{e.Image.Manga, !e.Image.Manga} {
|
||||
g := gift.New(splitFilter...)
|
||||
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))
|
||||
@ -253,15 +274,52 @@ 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(src image.Image, title string) (*epubzip.ZipImage, error) {
|
||||
func (e *EPUBImageProcessor) CoverTitleData(o *CoverTitleDataOptions) (*epubzip.ZipImage, error) {
|
||||
// Create a blur version of the cover
|
||||
g := gift.New(epubimagefilters.CoverTitle(title))
|
||||
dst := e.createImage(src, g.Bounds(src.Bounds()))
|
||||
g.Draw(dst, src)
|
||||
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/title.%s", e.Image.Format),
|
||||
fmt.Sprintf("OEBPS/Images/%s.%s", o.Name, e.Image.Format),
|
||||
e.Image.Format,
|
||||
dst,
|
||||
e.Image.Quality,
|
||||
|
@ -33,6 +33,7 @@ type Image struct {
|
||||
HasCover bool
|
||||
View *View
|
||||
GrayScale bool
|
||||
GrayScaleMode int
|
||||
Resize bool
|
||||
Format string
|
||||
}
|
||||
@ -41,6 +42,7 @@ type Options struct {
|
||||
Input string
|
||||
Output string
|
||||
Title string
|
||||
TitlePage int
|
||||
Author string
|
||||
LimitMb int
|
||||
StripFirstDirectoryFromToc bool
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
|
||||
type ContentOptions struct {
|
||||
Title string
|
||||
HasTitlePage bool
|
||||
UID string
|
||||
Author string
|
||||
Publisher string
|
||||
@ -124,9 +125,7 @@ func getMeta(o *ContentOptions) []tag {
|
||||
metas = append(metas, tag{"meta", tagAttrs{"name": "primary-writing-mode", "content": "horizontal-lr"}, ""})
|
||||
}
|
||||
|
||||
if o.Cover != nil {
|
||||
metas = append(metas, tag{"meta", tagAttrs{"name": "cover", "content": o.Cover.ImgKey()}, ""})
|
||||
}
|
||||
metas = append(metas, tag{"meta", tagAttrs{"name": "cover", "content": "img_cover"}, ""})
|
||||
|
||||
if o.Total > 1 {
|
||||
metas = append(
|
||||
@ -158,16 +157,19 @@ func getManifest(o *ContentOptions) []tag {
|
||||
items := []tag{
|
||||
{"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": "page_title", "href": "Text/title.xhtml", "media-type": "application/xhtml+xml"}, ""},
|
||||
{"item", tagAttrs{"id": "img_title", "href": fmt.Sprintf("Images/title.%s", o.ImageOptions.Format), "media-type": fmt.Sprintf("image/%s", o.ImageOptions.Format)}, ""},
|
||||
{"item", tagAttrs{"id": "page_cover", "href": "Text/cover.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)}, ""},
|
||||
}
|
||||
|
||||
if !o.ImageOptions.View.PortraitOnly {
|
||||
items = append(items, tag{"item", tagAttrs{"id": "space_title", "href": "Text/space_title.xhtml", "media-type": "application/xhtml+xml"}, ""})
|
||||
}
|
||||
if o.HasTitlePage {
|
||||
items = append(items,
|
||||
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.HasCover || o.Current > 1 {
|
||||
addTag(o.Cover, false)
|
||||
if !o.ImageOptions.View.PortraitOnly {
|
||||
items = append(items, tag{"item", tagAttrs{"id": "space_title", "href": "Text/space_title.xhtml", "media-type": "application/xhtml+xml"}, ""})
|
||||
}
|
||||
}
|
||||
|
||||
lastImage := o.Images[len(o.Images)-1]
|
||||
@ -202,9 +204,12 @@ func getSpineAuto(o *ContentOptions) []tag {
|
||||
return fmt.Sprintf("%s layout-blank", getSpread(false))
|
||||
}
|
||||
|
||||
spine := []tag{
|
||||
{"itemref", tagAttrs{"idref": "space_title", "properties": getSpreadBlank()}, ""},
|
||||
{"itemref", tagAttrs{"idref": "page_title", "properties": getSpread(false)}, ""},
|
||||
spine := []tag{}
|
||||
if o.HasTitlePage {
|
||||
spine = append(spine,
|
||||
tag{"itemref", tagAttrs{"idref": "space_title", "properties": getSpreadBlank()}, ""},
|
||||
tag{"itemref", tagAttrs{"idref": "page_title", "properties": getSpread(false)}, ""},
|
||||
)
|
||||
}
|
||||
for _, img := range o.Images {
|
||||
if img.DoublePage && o.ImageOptions.Manga == isOnTheRight {
|
||||
@ -234,8 +239,11 @@ func getSpineAuto(o *ContentOptions) []tag {
|
||||
}
|
||||
|
||||
func getSpinePortrait(o *ContentOptions) []tag {
|
||||
spine := []tag{
|
||||
{"itemref", tagAttrs{"idref": "page_title"}, ""},
|
||||
spine := []tag{}
|
||||
if o.HasTitlePage {
|
||||
spine = append(spine,
|
||||
tag{"itemref", tagAttrs{"idref": "page_title"}, ""},
|
||||
)
|
||||
}
|
||||
for _, img := range o.Images {
|
||||
spine = append(spine, tag{
|
||||
@ -249,10 +257,8 @@ func getSpinePortrait(o *ContentOptions) []tag {
|
||||
|
||||
// guide part of the content
|
||||
func getGuide(o *ContentOptions) []tag {
|
||||
guide := []tag{}
|
||||
if o.Cover != nil {
|
||||
guide = append(guide, tag{"reference", tagAttrs{"type": "cover", "title": "cover", "href": o.Cover.PagePath()}, ""})
|
||||
return []tag{
|
||||
{"reference", tagAttrs{"type": "cover", "title": "cover", "href": "Text/cover.xhtml"}, ""},
|
||||
{"reference", tagAttrs{"type": "text", "title": "content", "href": o.Images[0].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
|
||||
func Toc(title string, stripFirstDirectoryFromToc bool, images []*epubimage.Image) string {
|
||||
func Toc(title string, hasTitle bool, stripFirstDirectoryFromToc bool, images []*epubimage.Image) string {
|
||||
doc := etree.NewDocument()
|
||||
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
|
||||
doc.CreateDirective("DOCTYPE html")
|
||||
@ -55,7 +55,11 @@ func Toc(title string, stripFirstDirectoryFromToc bool, images []*epubimage.Imag
|
||||
|
||||
beginning := etree.NewElement("li")
|
||||
beginningLink := beginning.CreateElement("a")
|
||||
beginningLink.CreateAttr("href", "Text/title.xhtml")
|
||||
if hasTitle {
|
||||
beginningLink.CreateAttr("href", "Text/title.xhtml")
|
||||
} else {
|
||||
beginningLink.CreateAttr("href", images[0].PagePath())
|
||||
}
|
||||
beginningLink.CreateText(title)
|
||||
ol.InsertChildAt(0, beginning)
|
||||
|
||||
|
6
main.go
6
main.go
@ -105,6 +105,7 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
|
||||
Output: cmd.Options.Output,
|
||||
LimitMb: cmd.Options.LimitMb,
|
||||
Title: cmd.Options.Title,
|
||||
TitlePage: cmd.Options.TitlePage,
|
||||
Author: cmd.Options.Author,
|
||||
StripFirstDirectoryFromToc: cmd.Options.StripFirstDirectoryFromToc,
|
||||
SortPathMode: cmd.Options.SortPathMode,
|
||||
@ -113,8 +114,9 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
|
||||
DryVerbose: cmd.Options.DryVerbose,
|
||||
Quiet: cmd.Options.Quiet,
|
||||
Image: &epuboptions.Image{
|
||||
Quality: cmd.Options.Quality,
|
||||
GrayScale: cmd.Options.Grayscale,
|
||||
Quality: cmd.Options.Quality,
|
||||
GrayScale: cmd.Options.Grayscale,
|
||||
GrayScaleMode: cmd.Options.GrayscaleMode,
|
||||
Crop: &epuboptions.Crop{
|
||||
Enabled: cmd.Options.Crop,
|
||||
Left: cmd.Options.CropRatioLeft,
|
||||
|
Loading…
x
Reference in New Issue
Block a user