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 \
os.path.splitext(cpath)[1].lower() in ('.png', '.jpg', '.jpeg')
if cover_replacable:
from calibre.utils.magick_draw import save_cover_data_to, \
from calibre.utils.magick.draw import save_cover_data_to, \
identify
new_cover = PersistentTemporaryFile(suffix=os.path.splitext(cpath)[1])
resize_to = None

View File

@ -98,7 +98,7 @@ class CoverManager(object):
authors = [unicode(x) for x in m.creator if x.role == 'aut']
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),
32)]
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.search_query_parser import saved_searches, set_saved_searches
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:
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.fetch.simple import option_parser as web2disk_option_parser
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.ptempfile import PersistentTemporaryFile
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):
cfile.write(r.read())
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,
left=self.cover_margins[0],right=self.cover_margins[0],
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
'''
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 \
self.title.decode(preferred_encoding, 'replace')
date = strftime(self.timefmt)