Media processing, transcoding, display fixes
[mediagoblin.git] / mediagoblin / media_types / ascii / asciitoimage.py
CommitLineData
a246ccca 1# GNU MediaGoblin -- federated, autonomous media hosting
cf29e8a8 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
a246ccca
JW
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Affero General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import Image
18import ImageFont
19import ImageDraw
20import logging
21import pkg_resources
22import os
23
24_log = logging.getLogger(__name__)
25
c56d4b55 26
a246ccca
JW
27class AsciiToImage(object):
28 '''
29 Converter of ASCII art into image files, preserving whitespace
30
31 kwargs:
32 - font: Path to font file
33 default: fonts/Inconsolata.otf
34 - font_size: Font size, ``int``
35 default: 11
36 '''
a246ccca 37 def __init__(self, **kw):
196a5181 38 self._font = kw.get('font', pkg_resources.resource_filename(
a246ccca 39 'mediagoblin.media_types.ascii',
196a5181 40 os.path.join('fonts', 'Inconsolata.otf')))
a246ccca 41
196a5181 42 self._font_size = kw.get('font_size', 11)
a246ccca 43
a246ccca
JW
44 self._if = ImageFont.truetype(
45 self._font,
010d28b4
JW
46 self._font_size,
47 encoding='unic')
a246ccca 48
64da09e8
JW
49 _log.info('Font set to {0}, size {1}'.format(
50 self._font,
51 self._font_size))
52
a246ccca
JW
53 # ,-,-^-'-^'^-^'^-'^-.
54 # ( I am a wall socket )Oo, ___
55 # `-.,.-.,.-.-.,.-.--' ' `
56 # Get the size, in pixels of the '.' character
57 self._if_dims = self._if.getsize('.')
58 # `---'
59
60 def convert(self, text, destination):
61 # TODO: Detect if text is a file-like, if so, act accordingly
62 im = self._create_image(text)
63
64 # PIL's Image.save will handle both file-likes and paths
65 if im.save(destination):
66 _log.info('Saved image in {0}'.format(
67 destination))
68
69 def _create_image(self, text):
70 '''
71 Write characters to a PIL image canvas.
72
73 TODO:
74 - Character set detection and decoding,
75 http://pypi.python.org/pypi/chardet
76 '''
64da09e8 77 _log.debug('Drawing image')
010d28b4
JW
78 # Convert the input from str to unicode
79 text = text.decode('utf-8')
80
a246ccca
JW
81 # TODO: Account for alternative line endings
82 lines = text.split('\n')
83
84 line_lengths = [len(i) for i in lines]
85
86 # Calculate destination size based on text input and character size
87 im_dims = (
88 max(line_lengths) * self._if_dims[0],
89 len(line_lengths) * self._if_dims[1])
90
91 _log.info('Destination image dimensions will be {0}'.format(
92 im_dims))
93
94 im = Image.new(
95 'RGBA',
96 im_dims,
97 (255, 255, 255, 0))
98
99 draw = ImageDraw.Draw(im)
100
101 char_pos = [0, 0]
102
103 for line in lines:
104 line_length = len(line)
105
106 _log.debug('Writing line at {0}'.format(char_pos))
107
108 for _pos in range(0, line_length):
109 char = line[_pos]
110
111 px_pos = self._px_pos(char_pos)
112
010d28b4 113 _log.debug('Writing character "{0}" at {1} (px pos {2})'.format(
64da09e8 114 char.encode('ascii', 'replace'),
a246ccca
JW
115 char_pos,
116 px_pos))
117
118 draw.text(
119 px_pos,
120 char,
121 font=self._if,
122 fill=(0, 0, 0, 255))
123
124 char_pos[0] += 1
125
126 # Reset X position, increment Y position
127 char_pos[0] = 0
128 char_pos[1] += 1
129
130 return im
131
132 def _px_pos(self, char_pos):
133 '''
134 Helper function to calculate the pixel position based on
135 character position and character dimensions
136 '''
137 px_pos = [0, 0]
138 for index, val in zip(range(0, len(char_pos)), char_pos):
139 px_pos[index] = char_pos[index] * self._if_dims[index]
140
141 return px_pos