~ubuntu-branches/debian/wheezy/calibre/wheezy

« back to all changes in this revision

Viewing changes to src/calibre/utils/magick_draw.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2010-08-11 11:30:57 UTC
  • mfrom: (1.3.14 upstream)
  • mto: (29.3.2 oneiric)
  • mto: This revision was merged to the branch mainline in revision 31.
  • Revision ID: james.westby@ubuntu.com-20100811113057-2jhbcbavxw7wlt0c
Tags: 0.7.13+dfsg-1
* New upstream version.
* debian/control: Add python-routes recommends. (Closes: #590561)
* Convert to 3.0 (quilt) source format.
* Bump debhelper compat level to 7, and drop now obsolete
  DEB_DH_INSTALL_SOURCEDIR in debian/rules.
* debian/control: Add missing ${misc:Depends}.
* debian/control: Bump Standards-Version to 3.9.1.
* debian/copyright: Replace obsolete reference to
  /usr/share/common-licenses/BSD with their verbatim text from the original
  source.
* debian/rules: Remove invalid hashbang lines from *.recipe, these have no
  __main__.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
3
 
 
4
 
__license__   = 'GPL v3'
5
 
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
6
 
__docformat__ = 'restructuredtext en'
7
 
 
8
 
from ctypes import byref, c_double
9
 
 
10
 
import calibre.utils.PythonMagickWand as p
11
 
from calibre.ptempfile import TemporaryFile
12
 
from calibre.constants import filesystem_encoding, __appname__, __version__
13
 
 
14
 
# Font metrics {{{
15
 
class Rect(object):
16
 
 
17
 
    def __init__(self, left, top, right, bottom):
18
 
        self.left, self.top, self.right, self.bottom = left, top, right, bottom
19
 
 
20
 
    def __str__(self):
21
 
        return '(%s, %s) -- (%s, %s)'%(self.left, self.top, self.right,
22
 
                self.bottom)
23
 
 
24
 
class FontMetrics(object):
25
 
 
26
 
    def __init__(self, ret):
27
 
        self._attrs = []
28
 
        for i, x in enumerate(('char_width', 'char_height', 'ascender',
29
 
            'descender', 'text_width', 'text_height',
30
 
            'max_horizontal_advance')):
31
 
            setattr(self, x, ret[i])
32
 
            self._attrs.append(x)
33
 
        self.bounding_box = Rect(ret[7], ret[8], ret[9], ret[10])
34
 
        self.x, self.y = ret[11], ret[12]
35
 
        self._attrs.extend(['bounding_box', 'x', 'y'])
36
 
        self._attrs = tuple(self._attrs)
37
 
 
38
 
    def __str__(self):
39
 
        return '''FontMetrics:
40
 
            char_width: %s
41
 
            char_height: %s
42
 
            ascender: %s
43
 
            descender: %s
44
 
            text_width: %s
45
 
            text_height: %s
46
 
            max_horizontal_advance: %s
47
 
            bounding_box: %s
48
 
            x: %s
49
 
            y: %s
50
 
            '''%tuple([getattr(self, x) for x in self._attrs])
51
 
 
52
 
 
53
 
def get_font_metrics(image, d_wand, text):
54
 
    if isinstance(text, unicode):
55
 
        text = text.encode('utf-8')
56
 
    ret = p.MagickQueryFontMetrics(image, d_wand, text)
57
 
    return FontMetrics(ret)
58
 
 
59
 
# }}}
60
 
 
61
 
class TextLine(object):
62
 
 
63
 
    def __init__(self, text, font_size, bottom_margin=30, font_path=None):
64
 
        self.text, self.font_size, = text, font_size
65
 
        self.bottom_margin = bottom_margin
66
 
        self.font_path = font_path
67
 
 
68
 
    def __repr__(self):
69
 
        return u'TextLine:%r:%f'%(self.text, self.font_size)
70
 
 
71
 
def alloc_wand(name):
72
 
    ans = getattr(p, name)()
73
 
    if ans < 0:
74
 
        raise RuntimeError('Cannot create wand')
75
 
    return ans
76
 
 
77
 
def create_text_wand(font_size, font_path=None):
78
 
    if font_path is None:
79
 
        font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
80
 
    if isinstance(font_path, unicode):
81
 
        font_path = font_path.encode(filesystem_encoding)
82
 
    ans = alloc_wand('NewDrawingWand')
83
 
    if not p.DrawSetFont(ans, font_path):
84
 
        raise ValueError('Failed to set font to: '+font_path)
85
 
    p.DrawSetFontSize(ans, font_size)
86
 
    p.DrawSetGravity(ans, p.CenterGravity)
87
 
    p.DrawSetTextAntialias(ans, p.MagickTrue)
88
 
    return ans
89
 
 
90
 
 
91
 
def _get_line(img, dw, tokens, line_width):
92
 
    line, rest = tokens, []
93
 
    while True:
94
 
        m = get_font_metrics(img, dw, ' '.join(line))
95
 
        width, height = m.text_width, m.text_height
96
 
        if width < line_width:
97
 
            return line, rest
98
 
        rest = line[-1:] + rest
99
 
        line = line[:-1]
100
 
 
101
 
def annotate_img(img, dw, left, top, rotate, text,
102
 
        translate_from_top_left=True):
103
 
    if isinstance(text, unicode):
104
 
        text = text.encode('utf-8')
105
 
    if translate_from_top_left:
106
 
        m = get_font_metrics(img, dw, text)
107
 
        img_width = p.MagickGetImageWidth(img)
108
 
        img_height = p.MagickGetImageHeight(img)
109
 
        left = left - img_width/2. + m.text_width/2.
110
 
        top  = top - img_height/2. + m.text_height/2.
111
 
    p.MagickAnnotateImage(img, dw, left, top, rotate, text)
112
 
 
113
 
def draw_centered_line(img, dw, line, top):
114
 
    m = get_font_metrics(img, dw, line)
115
 
    width, height = m.text_width, m.text_height
116
 
    img_width = p.MagickGetImageWidth(img)
117
 
    left = max(int((img_width - width)/2.), 0)
118
 
    annotate_img(img, dw, left, top, 0, line)
119
 
    return top + height
120
 
 
121
 
def draw_centered_text(img, dw, text, top, margin=10):
122
 
    img_width = p.MagickGetImageWidth(img)
123
 
    tokens = text.split(' ')
124
 
    while tokens:
125
 
        line, tokens = _get_line(img, dw, tokens, img_width-2*margin)
126
 
        if not line:
127
 
            # Could not fit the first token on the line
128
 
            line = tokens[:1]
129
 
            tokens = tokens[1:]
130
 
        bottom = draw_centered_line(img, dw, ' '.join(line), top)
131
 
        top = bottom
132
 
    return top
133
 
 
134
 
def create_canvas(width, height, bgcolor):
135
 
    canvas = alloc_wand('NewMagickWand')
136
 
    p_wand = alloc_wand('NewPixelWand')
137
 
    p.PixelSetColor(p_wand, bgcolor)
138
 
    p.MagickNewImage(canvas, width, height, p_wand)
139
 
    p.DestroyPixelWand(p_wand)
140
 
    return canvas
141
 
 
142
 
def compose_image(canvas, image, left, top):
143
 
    p.MagickCompositeImage(canvas, image, p.OverCompositeOp, int(left),
144
 
            int(top))
145
 
 
146
 
def load_image(path):
147
 
    img = alloc_wand('NewMagickWand')
148
 
    if not p.MagickReadImage(img, path):
149
 
        severity = p.ExceptionType(0)
150
 
        msg = p.MagickGetException(img, byref(severity))
151
 
        raise IOError('Failed to read image from: %s: %s'
152
 
                %(path, msg))
153
 
    return img
154
 
 
155
 
def create_text_arc(text, font_size, font=None, bgcolor='white'):
156
 
    if isinstance(text, unicode):
157
 
        text = text.encode('utf-8')
158
 
 
159
 
    canvas = create_canvas(300, 300, bgcolor)
160
 
    tw = create_text_wand(font_size, font_path=font)
161
 
    m = get_font_metrics(canvas, tw, text)
162
 
    p.DestroyMagickWand(canvas)
163
 
    canvas = create_canvas(int(m.text_width)+20, int(m.text_height*3.5), bgcolor)
164
 
    p.MagickAnnotateImage(canvas, tw, 0, 0, 0, text)
165
 
    angle = c_double(120.)
166
 
    p.MagickDistortImage(canvas, 9, 1, byref(angle),
167
 
            p.MagickTrue)
168
 
    p.MagickTrimImage(canvas, 0)
169
 
    return canvas
170
 
 
171
 
def add_borders_to_image(path_to_image, left=0, top=0, right=0, bottom=0,
172
 
        border_color='white'):
173
 
    with p.ImageMagick():
174
 
        img = load_image(path_to_image)
175
 
        lwidth = p.MagickGetImageWidth(img)
176
 
        lheight = p.MagickGetImageHeight(img)
177
 
        canvas = create_canvas(lwidth+left+right, lheight+top+bottom,
178
 
                border_color)
179
 
        compose_image(canvas, img, left, top)
180
 
        p.DestroyMagickWand(img)
181
 
        p.MagickWriteImage(canvas,path_to_image)
182
 
        p.DestroyMagickWand(canvas)
183
 
 
184
 
def create_cover_page(top_lines, logo_path, width=590, height=750,
185
 
        bgcolor='white', output_format='jpg'):
186
 
    ans = None
187
 
    with p.ImageMagick():
188
 
        canvas = create_canvas(width, height, bgcolor)
189
 
 
190
 
        bottom = 10
191
 
        for line in top_lines:
192
 
            twand = create_text_wand(line.font_size, font_path=line.font_path)
193
 
            bottom = draw_centered_text(canvas, twand, line.text, bottom)
194
 
            bottom += line.bottom_margin
195
 
            p.DestroyDrawingWand(twand)
196
 
        bottom -= top_lines[-1].bottom_margin
197
 
 
198
 
        vanity = create_text_arc(__appname__ + ' ' + __version__, 24,
199
 
                font=P('fonts/liberation/LiberationMono-Regular.ttf'))
200
 
        lwidth = p.MagickGetImageWidth(vanity)
201
 
        lheight = p.MagickGetImageHeight(vanity)
202
 
        left = int(max(0, (width - lwidth)/2.))
203
 
        top  = height - lheight - 10
204
 
        compose_image(canvas, vanity, left, top)
205
 
 
206
 
        logo = load_image(logo_path)
207
 
        lwidth = p.MagickGetImageWidth(logo)
208
 
        lheight = p.MagickGetImageHeight(logo)
209
 
        left = int(max(0, (width - lwidth)/2.))
210
 
        top  = max(int((height - lheight)/2.), bottom+20)
211
 
        compose_image(canvas, logo, left, top)
212
 
        p.DestroyMagickWand(logo)
213
 
 
214
 
        with TemporaryFile('.'+output_format) as f:
215
 
            p.MagickWriteImage(canvas, f)
216
 
            with open(f, 'rb') as f:
217
 
                ans = f.read()
218
 
        p.DestroyMagickWand(canvas)
219
 
    return ans
220
 
 
221
 
def save_cover_data_to(data, path, bgcolor='white'):
222
 
    '''
223
 
    Saves image in data to path, in the format specified by the path
224
 
    extension. Composes the image onto a blank cancas so as to
225
 
    properly convert transparent images.
226
 
    '''
227
 
    with open(path, 'wb') as f:
228
 
        f.write(data)
229
 
    with p.ImageMagick():
230
 
        img = load_image(path)
231
 
        canvas = create_canvas(p.MagickGetImageWidth(img),
232
 
                p.MagickGetImageHeight(img), bgcolor)
233
 
        compose_image(canvas, img, 0, 0)
234
 
        p.MagickWriteImage(canvas, path)
235
 
        p.DestroyMagickWand(img)
236
 
        p.DestroyMagickWand(canvas)
237
 
 
238
 
def test():
239
 
    import subprocess
240
 
    with TemporaryFile('.png') as f:
241
 
        data = create_cover_page(
242
 
                [TextLine('A very long title indeed, don\'t you agree?', 42),
243
 
                TextLine('Mad Max & Mixy poo', 32)], I('library.png'))
244
 
        with open(f, 'wb') as g:
245
 
            g.write(data)
246
 
        subprocess.check_call(['gwenview', f])
247
 
 
248
 
if __name__ == '__main__':
249
 
    test()