Migrate all uses of magick_draw to new ImageMagick API

This commit is contained in:
Kovid Goyal 2010-08-04 12:58:53 -06:00
parent 9aa0fc428d
commit ffe6161940
5 changed files with 5 additions and 274 deletions

View File

@ -205,7 +205,7 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False):
cover_replacable = not reader.encryption_meta.is_encrypted(cpath) and \ cover_replacable = not reader.encryption_meta.is_encrypted(cpath) and \
os.path.splitext(cpath)[1].lower() in ('.png', '.jpg', '.jpeg') os.path.splitext(cpath)[1].lower() in ('.png', '.jpg', '.jpeg')
if cover_replacable: if cover_replacable:
from calibre.utils.magick_draw import save_cover_data_to, \ from calibre.utils.magick.draw import save_cover_data_to, \
identify identify
new_cover = PersistentTemporaryFile(suffix=os.path.splitext(cpath)[1]) new_cover = PersistentTemporaryFile(suffix=os.path.splitext(cpath)[1])
resize_to = None resize_to = None

View File

@ -98,7 +98,7 @@ class CoverManager(object):
authors = [unicode(x) for x in m.creator if x.role == 'aut'] authors = [unicode(x) for x in m.creator if x.role == 'aut']
try: try:
from calibre.utils.magick_draw import create_cover_page, TextLine from calibre.utils.magick.draw import create_cover_page, TextLine
lines = [TextLine(title, 44), TextLine(authors_to_string(authors), lines = [TextLine(title, 44), TextLine(authors_to_string(authors),
32)] 32)]
img_data = create_cover_page(lines, I('library.png')) img_data = create_cover_page(lines, I('library.png'))

View File

@ -32,7 +32,7 @@ from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp
from calibre.utils.config import prefs, tweaks from calibre.utils.config import prefs, tweaks
from calibre.utils.search_query_parser import saved_searches, set_saved_searches from calibre.utils.search_query_parser import saved_searches, set_saved_searches
from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format
from calibre.utils.magick_draw import save_cover_data_to from calibre.utils.magick.draw import save_cover_data_to
if iswindows: if iswindows:
import calibre.utils.winshell as winshell import calibre.utils.winshell as winshell

View File

@ -1,269 +0,0 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from ctypes import byref, c_double
import calibre.utils.PythonMagickWand as p
from calibre.ptempfile import TemporaryFile
from calibre.constants import filesystem_encoding, __appname__, __version__
# Font metrics {{{
class Rect(object):
def __init__(self, left, top, right, bottom):
self.left, self.top, self.right, self.bottom = left, top, right, bottom
def __str__(self):
return '(%s, %s) -- (%s, %s)'%(self.left, self.top, self.right,
self.bottom)
class FontMetrics(object):
def __init__(self, ret):
self._attrs = []
for i, x in enumerate(('char_width', 'char_height', 'ascender',
'descender', 'text_width', 'text_height',
'max_horizontal_advance')):
setattr(self, x, ret[i])
self._attrs.append(x)
self.bounding_box = Rect(ret[7], ret[8], ret[9], ret[10])
self.x, self.y = ret[11], ret[12]
self._attrs.extend(['bounding_box', 'x', 'y'])
self._attrs = tuple(self._attrs)
def __str__(self):
return '''FontMetrics:
char_width: %s
char_height: %s
ascender: %s
descender: %s
text_width: %s
text_height: %s
max_horizontal_advance: %s
bounding_box: %s
x: %s
y: %s
'''%tuple([getattr(self, x) for x in self._attrs])
def get_font_metrics(image, d_wand, text):
if isinstance(text, unicode):
text = text.encode('utf-8')
ret = p.MagickQueryFontMetrics(image, d_wand, text)
return FontMetrics(ret)
# }}}
class TextLine(object):
def __init__(self, text, font_size, bottom_margin=30, font_path=None):
self.text, self.font_size, = text, font_size
self.bottom_margin = bottom_margin
self.font_path = font_path
def __repr__(self):
return u'TextLine:%r:%f'%(self.text, self.font_size)
def alloc_wand(name):
ans = getattr(p, name)()
if ans < 0:
raise RuntimeError('Cannot create wand')
return ans
def create_text_wand(font_size, font_path=None):
if font_path is None:
font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
if isinstance(font_path, unicode):
font_path = font_path.encode(filesystem_encoding)
ans = alloc_wand('NewDrawingWand')
if not p.DrawSetFont(ans, font_path):
raise ValueError('Failed to set font to: '+font_path)
p.DrawSetFontSize(ans, font_size)
p.DrawSetGravity(ans, p.CenterGravity)
p.DrawSetTextAntialias(ans, p.MagickTrue)
return ans
def _get_line(img, dw, tokens, line_width):
line, rest = tokens, []
while True:
m = get_font_metrics(img, dw, ' '.join(line))
width, height = m.text_width, m.text_height
if width < line_width:
return line, rest
rest = line[-1:] + rest
line = line[:-1]
def annotate_img(img, dw, left, top, rotate, text,
translate_from_top_left=True):
if isinstance(text, unicode):
text = text.encode('utf-8')
if translate_from_top_left:
m = get_font_metrics(img, dw, text)
img_width = p.MagickGetImageWidth(img)
img_height = p.MagickGetImageHeight(img)
left = left - img_width/2. + m.text_width/2.
top = top - img_height/2. + m.text_height/2.
p.MagickAnnotateImage(img, dw, left, top, rotate, text)
def draw_centered_line(img, dw, line, top):
m = get_font_metrics(img, dw, line)
width, height = m.text_width, m.text_height
img_width = p.MagickGetImageWidth(img)
left = max(int((img_width - width)/2.), 0)
annotate_img(img, dw, left, top, 0, line)
return top + height
def draw_centered_text(img, dw, text, top, margin=10):
img_width = p.MagickGetImageWidth(img)
tokens = text.split(' ')
while tokens:
line, tokens = _get_line(img, dw, tokens, img_width-2*margin)
if not line:
# Could not fit the first token on the line
line = tokens[:1]
tokens = tokens[1:]
bottom = draw_centered_line(img, dw, ' '.join(line), top)
top = bottom
return top
def create_canvas(width, height, bgcolor):
canvas = alloc_wand('NewMagickWand')
p_wand = alloc_wand('NewPixelWand')
p.PixelSetColor(p_wand, bgcolor)
p.MagickNewImage(canvas, width, height, p_wand)
p.DestroyPixelWand(p_wand)
return canvas
def compose_image(canvas, image, left, top):
p.MagickCompositeImage(canvas, image, p.OverCompositeOp, int(left),
int(top))
def load_image(path):
if isinstance(path, unicode):
path = path.encode(filesystem_encoding)
img = alloc_wand('NewMagickWand')
if not p.MagickReadImage(img, path):
severity = p.ExceptionType(0)
msg = p.MagickGetException(img, byref(severity))
raise IOError('Failed to read image from: %s: %s'
%(path, msg))
return img
def create_text_arc(text, font_size, font=None, bgcolor='white'):
if isinstance(text, unicode):
text = text.encode('utf-8')
canvas = create_canvas(300, 300, bgcolor)
tw = create_text_wand(font_size, font_path=font)
m = get_font_metrics(canvas, tw, text)
p.DestroyMagickWand(canvas)
canvas = create_canvas(int(m.text_width)+20, int(m.text_height*3.5), bgcolor)
p.MagickAnnotateImage(canvas, tw, 0, 0, 0, text)
angle = c_double(120.)
p.MagickDistortImage(canvas, 9, 1, byref(angle),
p.MagickTrue)
p.MagickTrimImage(canvas, 0)
return canvas
def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0,
border_color='white'):
with p.ImageMagick():
img = load_image(path_to_image)
lwidth = p.MagickGetImageWidth(img)
lheight = p.MagickGetImageHeight(img)
canvas = create_canvas(lwidth+left+right, lheight+top+bottom,
border_color)
compose_image(canvas, img, left, top)
p.DestroyMagickWand(img)
p.MagickWriteImage(canvas,path_to_image)
p.DestroyMagickWand(canvas)
def create_cover_page(top_lines, logo_path, width=590, height=750,
bgcolor='white', output_format='jpg'):
ans = None
with p.ImageMagick():
canvas = create_canvas(width, height, bgcolor)
bottom = 10
for line in top_lines:
twand = create_text_wand(line.font_size, font_path=line.font_path)
bottom = draw_centered_text(canvas, twand, line.text, bottom)
bottom += line.bottom_margin
p.DestroyDrawingWand(twand)
bottom -= top_lines[-1].bottom_margin
vanity = create_text_arc(__appname__ + ' ' + __version__, 24,
font=P('fonts/liberation/LiberationMono-Regular.ttf'))
lwidth = p.MagickGetImageWidth(vanity)
lheight = p.MagickGetImageHeight(vanity)
left = int(max(0, (width - lwidth)/2.))
top = height - lheight - 10
compose_image(canvas, vanity, left, top)
logo = load_image(logo_path)
lwidth = p.MagickGetImageWidth(logo)
lheight = p.MagickGetImageHeight(logo)
left = int(max(0, (width - lwidth)/2.))
top = max(int((height - lheight)/2.), bottom+20)
compose_image(canvas, logo, left, top)
p.DestroyMagickWand(logo)
with TemporaryFile('.'+output_format) as f:
p.MagickWriteImage(canvas, f)
with open(f, 'rb') as f:
ans = f.read()
p.DestroyMagickWand(canvas)
return ans
def save_cover_data_to(data, path, bgcolor='white', resize_to=None):
'''
Saves image in data to path, in the format specified by the path
extension. Composes the image onto a blank canvas so as to
properly convert transparent images.
'''
with open(path, 'wb') as f:
f.write(data)
with p.ImageMagick():
img = load_image(path)
if resize_to is not None:
p.MagickResizeImage(img, resize_to[0], resize_to[1], p.CatromFilter, 1.0)
canvas = create_canvas(p.MagickGetImageWidth(img),
p.MagickGetImageHeight(img), bgcolor)
compose_image(canvas, img, 0, 0)
p.MagickWriteImage(canvas, path)
p.DestroyMagickWand(img)
p.DestroyMagickWand(canvas)
def identify(path):
'''
Identify the image at path. Returns a 3-tuple
(width, height, format)
or raises an IOError.
'''
with p.ImageMagick():
img = load_image(path)
width = p.MagickGetImageWidth(img)
height = p.MagickGetImageHeight(img)
fmt = p.MagickGetImageFormat(img)
if not fmt:
fmt = ''
fmt = fmt.decode('utf-8', 'replace')
return (width, height, fmt)
def test():
import subprocess
with TemporaryFile('.png') as f:
data = create_cover_page(
[TextLine('A very long title indeed, don\'t you agree?', 42),
TextLine('Mad Max & Mixy poo', 32)], I('library.png'))
with open(f, 'wb') as g:
g.write(data)
subprocess.check_call(['gwenview', f])
if __name__ == '__main__':
test()

View File

@ -24,7 +24,6 @@ from calibre.ebooks.metadata import MetaInformation
from calibre.web.feeds import feed_from_xml, templates, feeds_from_index, Feed from calibre.web.feeds import feed_from_xml, templates, feeds_from_index, Feed
from calibre.web.fetch.simple import option_parser as web2disk_option_parser from calibre.web.fetch.simple import option_parser as web2disk_option_parser
from calibre.web.fetch.simple import RecursiveFetcher from calibre.web.fetch.simple import RecursiveFetcher
from calibre.utils.magick_draw import add_borders_to_image
from calibre.utils.threadpool import WorkRequest, ThreadPool, NoResultsPending from calibre.utils.threadpool import WorkRequest, ThreadPool, NoResultsPending
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.date import now as nowf from calibre.utils.date import now as nowf
@ -964,6 +963,7 @@ class BasicNewsRecipe(Recipe):
with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r): with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r):
cfile.write(r.read()) cfile.write(r.read())
if self.cover_margins[0] or self.cover_margins[1]: if self.cover_margins[0] or self.cover_margins[1]:
from calibre.utils.magick.draw import add_borders_to_image
add_borders_to_image(cpath, add_borders_to_image(cpath,
left=self.cover_margins[0],right=self.cover_margins[0], left=self.cover_margins[0],right=self.cover_margins[0],
top=self.cover_margins[1],bottom=self.cover_margins[1], top=self.cover_margins[1],bottom=self.cover_margins[1],
@ -1018,7 +1018,7 @@ class BasicNewsRecipe(Recipe):
Create a generic cover for recipes that dont have a cover Create a generic cover for recipes that dont have a cover
''' '''
try: try:
from calibre.utils.magick_draw import create_cover_page, TextLine from calibre.utils.magick.draw import create_cover_page, TextLine
title = self.title if isinstance(self.title, unicode) else \ title = self.title if isinstance(self.title, unicode) else \
self.title.decode(preferred_encoding, 'replace') self.title.decode(preferred_encoding, 'replace')
date = strftime(self.timefmt) date = strftime(self.timefmt)