mirror of
https://github.com/celogeek/go-comic-converter.git
synced 2025-05-25 00:02:37 +02:00
cover improvement
include part in cover if totalParts > 1 cleaner cover in grayscale
This commit is contained in:
parent
4a1ae516ea
commit
0de6cc9acd
@ -99,6 +99,49 @@ 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 := ""
|
||||
@ -134,7 +177,16 @@ func (e *ePub) writeTitleImage(wz *epubzip.EPUBZip, img *epubimage.Image, title
|
||||
return err
|
||||
}
|
||||
|
||||
coverTitle, err := e.imageProcessor.CoverTitleData(img.Raw, title)
|
||||
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
|
||||
}
|
||||
@ -184,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)
|
||||
@ -203,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
|
||||
@ -377,16 +423,12 @@ func (e *ePub) Write() error {
|
||||
}
|
||||
}
|
||||
|
||||
if err = e.writeTitleImage(wz, part.Cover, title); err != nil {
|
||||
if err = e.writeCoverImage(wz, part.Cover, i+1, totalParts); 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
|
||||
}
|
||||
if err = e.writeTitleImage(wz, part.Cover, title); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastImage := part.Images[len(part.Images)-1]
|
||||
|
@ -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,11 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
|
||||
filters = append(filters, f)
|
||||
}
|
||||
|
||||
if e.Image.GrayScale {
|
||||
filters = append(filters, gift.Grayscale())
|
||||
splitFilters = append(splitFilters, gift.Grayscale())
|
||||
}
|
||||
|
||||
filters = append(filters, epubimagefilters.Pixel())
|
||||
|
||||
// convert
|
||||
@ -240,7 +246,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 +259,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,
|
||||
|
@ -124,9 +124,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(
|
||||
@ -160,16 +158,14 @@ func getManifest(o *ContentOptions) []tag {
|
||||
{"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.ImageOptions.HasCover || o.Current > 1 {
|
||||
addTag(o.Cover, false)
|
||||
}
|
||||
|
||||
lastImage := o.Images[len(o.Images)-1]
|
||||
for _, img := range o.Images {
|
||||
addTag(img, !o.ImageOptions.View.PortraitOnly && (img.DoublePage || (img.Part == 0 && img == lastImage)))
|
||||
@ -249,10 +245,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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user