diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py index e181b68975..c2318efa0c 100644 --- a/src/calibre/ebooks/metadata/epub.py +++ b/src/calibre/ebooks/metadata/epub.py @@ -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 diff --git a/src/calibre/ebooks/oeb/transforms/cover.py b/src/calibre/ebooks/oeb/transforms/cover.py index 83c5ec93e4..83b7b5d3c1 100644 --- a/src/calibre/ebooks/oeb/transforms/cover.py +++ b/src/calibre/ebooks/oeb/transforms/cover.py @@ -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')) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 02bbac1eb8..d2cf05681e 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -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 diff --git a/src/calibre/utils/magick_draw.py b/src/calibre/utils/magick_draw.py deleted file mode 100644 index 9c2e46ac9f..0000000000 --- a/src/calibre/utils/magick_draw.py +++ /dev/null @@ -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 ' -__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() diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 269f710879..4a2696d24e 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -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)