mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Cover generation work
Etch the text, add a gradient based style, allow styles to specify page margins, more useful test() function that displays all color and style variants at once.
This commit is contained in:
parent
52035d6dcf
commit
08cfe8029a
@ -16,7 +16,7 @@ from itertools import chain, repeat
|
|||||||
|
|
||||||
from PyQt5.Qt import (
|
from PyQt5.Qt import (
|
||||||
QImage, Qt, QFont, QPainter, QPointF, QTextLayout, QTextOption,
|
QImage, Qt, QFont, QPainter, QPointF, QTextLayout, QTextOption,
|
||||||
QFontMetrics, QTextCharFormat, QColor, QRect
|
QFontMetrics, QTextCharFormat, QColor, QRect, QBrush, QLinearGradient
|
||||||
)
|
)
|
||||||
|
|
||||||
from calibre import force_unicode
|
from calibre import force_unicode
|
||||||
@ -170,14 +170,19 @@ class Block(object):
|
|||||||
def draw(self, painter):
|
def draw(self, painter):
|
||||||
for l in self.layouts:
|
for l in self.layouts:
|
||||||
if hasattr(l, 'draw'):
|
if hasattr(l, 'draw'):
|
||||||
|
# Etch effect for the text
|
||||||
|
painter.save()
|
||||||
|
painter.setPen(QColor(255, 255, 255, 125))
|
||||||
|
l.draw(painter, QPointF(1, 1))
|
||||||
|
painter.restore()
|
||||||
l.draw(painter, QPointF())
|
l.draw(painter, QPointF())
|
||||||
|
|
||||||
def layout_text(prefs, img, title, subtitle, footer, max_height, style, hmargin=50, vmargin=50):
|
def layout_text(prefs, img, title, subtitle, footer, max_height, style):
|
||||||
width = img.width() - 2 * hmargin
|
width = img.width() - 2 * style.hmargin
|
||||||
title_font = QFont(prefs.title_font_family)
|
title_font = QFont(prefs.title_font_family)
|
||||||
title_font.setPixelSize(prefs.title_font_size)
|
title_font.setPixelSize(prefs.title_font_size)
|
||||||
title_block = Block(title, width, title_font, img, max_height, style.TITLE_ALIGN)
|
title_block = Block(title, width, title_font, img, max_height, style.TITLE_ALIGN)
|
||||||
title_block.position = hmargin, vmargin
|
title_block.position = style.hmargin, style.vmargin
|
||||||
subtitle_block = Block()
|
subtitle_block = Block()
|
||||||
if subtitle:
|
if subtitle:
|
||||||
subtitle_font = QFont(prefs.subtitle_font_family)
|
subtitle_font = QFont(prefs.subtitle_font_family)
|
||||||
@ -185,12 +190,12 @@ def layout_text(prefs, img, title, subtitle, footer, max_height, style, hmargin=
|
|||||||
gap = 2 * title_block.leading
|
gap = 2 * title_block.leading
|
||||||
mh = max_height - title_block.height - gap
|
mh = max_height - title_block.height - gap
|
||||||
subtitle_block = Block(subtitle, width, subtitle_font, img, mh, style.SUBTITLE_ALIGN)
|
subtitle_block = Block(subtitle, width, subtitle_font, img, mh, style.SUBTITLE_ALIGN)
|
||||||
subtitle_block.position = hmargin, title_block.position[0] + title_block.height + gap
|
subtitle_block.position = style.hmargin, title_block.position[0] + title_block.height + gap
|
||||||
|
|
||||||
footer_font = QFont(prefs.footer_font_family)
|
footer_font = QFont(prefs.footer_font_family)
|
||||||
footer_font.setPixelSize(prefs.footer_font_size)
|
footer_font.setPixelSize(prefs.footer_font_size)
|
||||||
footer_block = Block(footer, width, footer_font, img, max_height, style.FOOTER_ALIGN)
|
footer_block = Block(footer, width, footer_font, img, max_height, style.FOOTER_ALIGN)
|
||||||
footer_block.position = hmargin, img.height() - vmargin - footer_block.height
|
footer_block.position = style.hmargin, img.height() - style.vmargin - footer_block.height
|
||||||
|
|
||||||
return title_block, subtitle_block, footer_block
|
return title_block, subtitle_block, footer_block
|
||||||
|
|
||||||
@ -240,7 +245,7 @@ def format_text(mi, prefs):
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
default_color_themes = {
|
default_color_themes = {
|
||||||
'Mocha': 'e8d9ac c7b07b 564628 000000',
|
'Mocha': 'e8d9ac c7b07b 564628 382d1a',
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorTheme = namedtuple('ColorTheme', 'color1 color2 contrast_color1 contrast_color2')
|
ColorTheme = namedtuple('ColorTheme', 'color1 color2 contrast_color1 contrast_color2')
|
||||||
@ -273,8 +278,13 @@ class Style(object):
|
|||||||
|
|
||||||
TITLE_ALIGN = SUBTITLE_ALIGN = FOOTER_ALIGN = Qt.AlignHCenter | Qt.AlignTop
|
TITLE_ALIGN = SUBTITLE_ALIGN = FOOTER_ALIGN = Qt.AlignHCenter | Qt.AlignTop
|
||||||
|
|
||||||
def __init__(self, color_theme):
|
def __init__(self, color_theme, prefs):
|
||||||
self.load_colors(color_theme)
|
self.load_colors(color_theme)
|
||||||
|
self.calculate_margins(prefs)
|
||||||
|
|
||||||
|
def calculate_margins(self, prefs):
|
||||||
|
self.hmargin = (50 / 600) * prefs.cover_width
|
||||||
|
self.vmargin = (50 / 800) * prefs.cover_height
|
||||||
|
|
||||||
def load_colors(self, color_theme):
|
def load_colors(self, color_theme):
|
||||||
self.color1 = color(color_theme, 'color1', Qt.white)
|
self.color1 = color(color_theme, 'color1', Qt.white)
|
||||||
@ -299,16 +309,36 @@ class Half(Style):
|
|||||||
|
|
||||||
NAME = 'Half and Half'
|
NAME = 'Half and Half'
|
||||||
GUI_NAME = _('Half and Half')
|
GUI_NAME = _('Half and Half')
|
||||||
|
|
||||||
|
def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block):
|
||||||
|
g = QLinearGradient(QPointF(0, 0), QPointF(0, rect.height()))
|
||||||
|
g.setStops([(0, self.color1), (0.7, self.color2), (1, self.color1)])
|
||||||
|
painter.fillRect(rect, QBrush(g))
|
||||||
|
return self.ccolor1, self.ccolor1, self.ccolor1
|
||||||
|
|
||||||
|
class Blocks(Style):
|
||||||
|
|
||||||
|
NAME = 'Blocks'
|
||||||
|
GUI_NAME = _('Blocks')
|
||||||
FOOTER_ALIGN = Qt.AlignRight | Qt.AlignTop
|
FOOTER_ALIGN = Qt.AlignRight | Qt.AlignTop
|
||||||
|
|
||||||
def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block):
|
def __call__(self, painter, rect, color_theme, title_block, subtitle_block, footer_block):
|
||||||
painter.fillRect(rect, self.color1)
|
painter.fillRect(rect, self.color1)
|
||||||
r = rect.adjusted(0, 0, 0, -rect.height() // 2)
|
y = rect.height() - rect.height() // 3
|
||||||
|
r = QRect(rect)
|
||||||
|
r.setBottom(y)
|
||||||
painter.fillRect(rect, self.color1)
|
painter.fillRect(rect, self.color1)
|
||||||
r = rect.adjusted(0, rect.height() // 2, 0, 0)
|
r = QRect(rect)
|
||||||
|
r.setTop(y)
|
||||||
painter.fillRect(r, self.color2)
|
painter.fillRect(r, self.color2)
|
||||||
return self.ccolor1, self.ccolor1, self.ccolor2
|
return self.ccolor1, self.ccolor1, self.ccolor2
|
||||||
|
|
||||||
|
def all_styles():
|
||||||
|
return set(
|
||||||
|
x.NAME for x in globals().itervalues() if
|
||||||
|
isinstance(x, type) and issubclass(x, Style) and x is not Style
|
||||||
|
)
|
||||||
|
|
||||||
def load_styles(prefs):
|
def load_styles(prefs):
|
||||||
disabled = frozenset(prefs.disabled_styles)
|
disabled = frozenset(prefs.disabled_styles)
|
||||||
return tuple(x for x in globals().itervalues() if
|
return tuple(x for x in globals().itervalues() if
|
||||||
@ -321,12 +351,11 @@ def generate_cover(mi, prefs=None, as_qimage=False):
|
|||||||
prefs = {k:prefs.get(k) for k in cprefs.defaults}
|
prefs = {k:prefs.get(k) for k in cprefs.defaults}
|
||||||
prefs = Prefs(**prefs)
|
prefs = Prefs(**prefs)
|
||||||
color_theme = choice(load_color_themes(prefs))
|
color_theme = choice(load_color_themes(prefs))
|
||||||
style = choice(load_styles(prefs))(color_theme)
|
style = choice(load_styles(prefs))(color_theme, prefs)
|
||||||
title, subtitle, footer = format_text(mi, prefs)
|
title, subtitle, footer = format_text(mi, prefs)
|
||||||
img = QImage(prefs.cover_width, prefs.cover_height, QImage.Format_ARGB32)
|
img = QImage(prefs.cover_width, prefs.cover_height, QImage.Format_ARGB32)
|
||||||
hmargin = vmargin = 50
|
|
||||||
title_block, subtitle_block, footer_block = layout_text(
|
title_block, subtitle_block, footer_block = layout_text(
|
||||||
prefs, img, title, subtitle, footer, img.height() // 3, style, hmargin, vmargin)
|
prefs, img, title, subtitle, footer, img.height() // 3, style)
|
||||||
p = QPainter(img)
|
p = QPainter(img)
|
||||||
rect = QRect(0, 0, img.width(), img.height())
|
rect = QRect(0, 0, img.width(), img.height())
|
||||||
colors = style(p, rect, color_theme, title_block, subtitle_block, footer_block)
|
colors = style(p, rect, color_theme, title_block, subtitle_block, footer_block)
|
||||||
@ -348,28 +377,40 @@ def override_prefs(base_prefs, **overrides):
|
|||||||
ans['disabled_color_themes'] = all_themes
|
ans['disabled_color_themes'] = all_themes
|
||||||
override_style = overrides.get('override_style')
|
override_style = overrides.get('override_style')
|
||||||
if override_style is not None:
|
if override_style is not None:
|
||||||
all_styles = set(
|
styles = all_styles()
|
||||||
x.NAME for x in globals().itervalues() if
|
if override_style in styles:
|
||||||
isinstance(x, type) and issubclass(x, Style) and x is not Style
|
styles.discard(override_style)
|
||||||
)
|
ans['disabled_styles'] = styles
|
||||||
if override_style in all_styles:
|
|
||||||
all_styles.discard(override_style)
|
|
||||||
ans['disabled_styles'] = all_styles
|
|
||||||
|
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
from PyQt5.Qt import QLabel, QApplication, QPixmap, QMainWindow
|
from PyQt5.Qt import QLabel, QApplication, QPixmap, QMainWindow, QWidget, QScrollArea, QGridLayout
|
||||||
from calibre.ebooks.metadata.book.base import Metadata
|
from calibre.ebooks.metadata.book.base import Metadata
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
mi = Metadata('An algorithmic cover', ['Kovid Goyal', 'John P. Doe', 'Author'])
|
mi = Metadata('xxx', ['Kovid Goyal', 'John Q. Doe', 'Author'])
|
||||||
mi.series = 'A Series of Tests'
|
mi.series = 'A series of styles'
|
||||||
mi.series_index = 3
|
|
||||||
img = generate_cover(mi, as_qimage=True)
|
|
||||||
l = QLabel()
|
|
||||||
l.setPixmap(QPixmap.fromImage(img))
|
|
||||||
m = QMainWindow()
|
m = QMainWindow()
|
||||||
m.setCentralWidget(l)
|
sa = QScrollArea(m)
|
||||||
|
w = QWidget(m)
|
||||||
|
sa.setWidget(w)
|
||||||
|
l = QGridLayout(w)
|
||||||
|
w.setLayout(l), l.setSpacing(30)
|
||||||
|
labels = []
|
||||||
|
for r, color in enumerate(sorted(default_color_themes)):
|
||||||
|
for c, style in enumerate(sorted(all_styles())):
|
||||||
|
mi.series_index = c + 1
|
||||||
|
mi.title = 'An algorithmic cover [%s]' % color
|
||||||
|
prefs = override_prefs(cprefs, override_color_theme=color, override_style=style)
|
||||||
|
for x in ('cover_width', 'cover_height', 'title_font_size', 'subtitle_font_size', 'footer_font_size'):
|
||||||
|
prefs[x] //= 2
|
||||||
|
img = generate_cover(mi, prefs=prefs, as_qimage=True)
|
||||||
|
la = QLabel()
|
||||||
|
la.setPixmap(QPixmap.fromImage(img))
|
||||||
|
l.addWidget(la, r, c)
|
||||||
|
labels.append(la)
|
||||||
|
m.setCentralWidget(sa)
|
||||||
|
w.resize(w.sizeHint())
|
||||||
m.show()
|
m.show()
|
||||||
app.exec_()
|
app.exec_()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user