~ubuntu-branches/ubuntu/quantal/enigmail/quantal-security

« back to all changes in this revision

Viewing changes to build/pypng/plan9topng.py

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2013-09-13 16:02:15 UTC
  • mfrom: (0.12.16)
  • Revision ID: package-import@ubuntu.com-20130913160215-u3g8nmwa0pdwagwc
Tags: 2:1.5.2-0ubuntu0.12.10.1
* New upstream release v1.5.2 for Thunderbird 24

* Build enigmail using a stripped down Thunderbird 17 build system, as it's
  now quite difficult to build the way we were doing previously, with the
  latest Firefox build system
* Add debian/patches/no_libxpcom.patch - Don't link against libxpcom, as it
  doesn't exist anymore (but exists in the build system)
* Add debian/patches/use_sdk.patch - Use the SDK version of xpt.py and
  friends
* Drop debian/patches/ipc-pipe_rename.diff (not needed anymore)
* Drop debian/patches/makefile_depth.diff (not needed anymore)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# $Rev: 184 $
 
3
# $URL: http://pypng.googlecode.com/svn/trunk/code/plan9topng.py $
 
4
 
 
5
# Imported from //depot/prj/plan9topam/master/code/plan9topam.py#4 on
 
6
# 2009-06-15.
 
7
 
 
8
"""Command line tool to convert from Plan 9 image format to PNG format.
 
9
 
 
10
Plan 9 image format description:
 
11
http://plan9.bell-labs.com/magic/man2html/6/image
 
12
"""
 
13
 
 
14
# http://www.python.org/doc/2.3.5/lib/module-itertools.html
 
15
import itertools
 
16
# http://www.python.org/doc/2.3.5/lib/module-re.html
 
17
import re
 
18
# http://www.python.org/doc/2.3.5/lib/module-sys.html
 
19
import sys
 
20
 
 
21
def block(s, n):
 
22
    # See http://www.python.org/doc/2.6.2/library/functions.html#zip
 
23
    return zip(*[iter(s)]*n)
 
24
 
 
25
def convert(f, output=sys.stdout) :
 
26
  """Convert Plan 9 file to PNG format.  Works with either uncompressed
 
27
  or compressed files.
 
28
  """
 
29
 
 
30
  r = f.read(11)
 
31
  if r == 'compressed\n' :
 
32
    png(output, *decompress(f))
 
33
  else :
 
34
    png(output, *glue(f, r))
 
35
 
 
36
 
 
37
def glue(f, r) :
 
38
  """Return (metadata, stream) pair where `r` is the initial portion of
 
39
  the metadata that has already been read from the stream `f`.
 
40
  """
 
41
 
 
42
  r = r + f.read(60-len(r))
 
43
  return (r, f)
 
44
 
 
45
def meta(r) :
 
46
  """Convert 60 character string `r`, the metadata from an image file.
 
47
  Returns a 5-tuple (*chan*,*minx*,*miny*,*limx*,*limy*).  5-tuples may
 
48
  settle into lists in transit.
 
49
  
 
50
  As per http://plan9.bell-labs.com/magic/man2html/6/image the metadata
 
51
  comprises 5 words separated by blanks.  As it happens each word starts
 
52
  at an index that is a multiple of 12, but this routine does not care
 
53
  about that."""
 
54
 
 
55
  r = r.split()
 
56
  # :todo: raise FormatError
 
57
  assert len(r) == 5
 
58
  r = [r[0]] + map(int, r[1:])
 
59
  return r
 
60
 
 
61
def bitdepthof(pixel) :
 
62
    """Return the bitdepth for a Plan9 pixel format string."""
 
63
 
 
64
    maxd = 0
 
65
    for c in re.findall(r'[a-z]\d*', pixel) :
 
66
        if c[0] != 'x':
 
67
            maxd = max(maxd, int(c[1:]))
 
68
    return maxd
 
69
 
 
70
def maxvalof(pixel):
 
71
  """Return the netpbm MAXVAL for a Plan9 pixel format string."""
 
72
 
 
73
  bitdepth = bitdepthof(pixel)
 
74
  return (2**bitdepth)-1
 
75
 
 
76
def pixmeta(metadata, f) :
 
77
    """Convert (uncompressed) Plan 9 image file to pair of (*metadata*,
 
78
    *pixels*).  This is intended to be used by PyPNG format.  *metadata*
 
79
    is the metadata returned in a dictionary, *pixels* is an iterator that
 
80
    yields each row in boxed row flat pixel format.
 
81
 
 
82
    `f`, the input file, should be cued up to the start of the image data.
 
83
    """
 
84
 
 
85
    chan,minx,miny,limx,limy = metadata
 
86
    rows = limy - miny
 
87
    width = limx - minx
 
88
    nchans = len(re.findall('[a-wyz]', chan))
 
89
    alpha = 'a' in chan
 
90
    # Iverson's convention for the win!
 
91
    ncolour = nchans - alpha
 
92
    greyscale = ncolour == 1
 
93
    bitdepth = bitdepthof(chan)
 
94
    maxval = 2**bitdepth - 1
 
95
    # PNG style metadata
 
96
    meta=dict(size=(width,rows), bitdepth=bitdepthof(chan),
 
97
      greyscale=greyscale, alpha=alpha, planes=nchans)
 
98
 
 
99
    return itertools.imap(lambda x: itertools.chain(*x),
 
100
      block(unpack(f, rows, width, chan, maxval), width)), meta
 
101
 
 
102
def png(out, metadata, f):
 
103
    """Convert to PNG format.  `metadata` should be a Plan9 5-tuple; `f`
 
104
    the input file (see :meth:`pixmeta`).
 
105
    """
 
106
 
 
107
    import png
 
108
 
 
109
    pixels,meta = pixmeta(metadata, f)
 
110
    p = png.Writer(**meta)
 
111
    p.write(out, pixels)
 
112
 
 
113
def spam():
 
114
  """Not really spam, but old PAM code, which is in limbo."""
 
115
 
 
116
  if nchans == 3 or nchans == 1 :
 
117
    # PGM (P5) or PPM (P6) format.
 
118
    output.write('P%d\n%d %d %d\n' % (5+(nchans==3), width, rows, maxval))
 
119
  else :
 
120
    # PAM format.
 
121
    output.write("""P7
 
122
WIDTH %d
 
123
HEIGHT %d
 
124
DEPTH %d
 
125
MAXVAL %d
 
126
""" % (width, rows, nchans, maxval))
 
127
 
 
128
def unpack(f, rows, width, pixel, maxval) :
 
129
  """Unpack `f` into pixels.  Assumes the pixel format is such that the depth
 
130
  is either a multiple or a divisor of 8.
 
131
  `f` is assumed to be an iterator that returns blocks of input such
 
132
  that each block contains a whole number of pixels.  An iterator is
 
133
  returned that yields each pixel as an n-tuple.  `pixel` describes the
 
134
  pixel format using the Plan9 syntax ("k8", "r8g8b8", and so on).
 
135
  """
 
136
 
 
137
  def mask(w) :
 
138
    """An integer, to be used as a mask, with bottom `w` bits set to 1."""
 
139
 
 
140
    return (1 << w)-1
 
141
 
 
142
  def deblock(f, depth, width) :
 
143
    """A "packer" used to convert multiple bytes into single pixels.
 
144
    `depth` is the pixel depth in bits (>= 8), `width` is the row width in
 
145
    pixels.
 
146
    """
 
147
 
 
148
    w = depth // 8
 
149
    i = 0
 
150
    for block in f :
 
151
      for i in range(len(block)//w) :
 
152
        p = block[w*i:w*(i+1)]
 
153
        i += w
 
154
        # Convert p to little-endian integer, x
 
155
        x = 0
 
156
        s = 1 # scale
 
157
        for j in p :
 
158
          x += s * ord(j)
 
159
          s <<= 8
 
160
        yield x
 
161
 
 
162
  def bitfunge(f, depth, width) :
 
163
    """A "packer" used to convert single bytes into multiple pixels.
 
164
    Depth is the pixel depth (< 8), width is the row width in pixels.
 
165
    """
 
166
 
 
167
    for block in f :
 
168
      col = 0
 
169
      for i in block :
 
170
        x = ord(i)
 
171
        for j in range(8/depth) :
 
172
          yield x >> (8 - depth)
 
173
          col += 1
 
174
          if col == width :
 
175
            # A row-end forces a new byte even if we haven't consumed
 
176
            # all of the current byte.  Effectively rows are bit-padded
 
177
            # to make a whole number of bytes.
 
178
            col = 0
 
179
            break
 
180
          x <<= depth
 
181
 
 
182
  # number of bits in each channel
 
183
  chan = map(int, re.findall(r'\d+', pixel))
 
184
  # type of each channel
 
185
  type = re.findall('[a-z]', pixel)
 
186
 
 
187
  depth = sum(chan)
 
188
 
 
189
  # According to the value of depth pick a "packer" that either gathers
 
190
  # multiple bytes into a single pixel (for depth >= 8) or split bytes
 
191
  # into several pixels (for depth < 8)
 
192
  if depth >= 8 :
 
193
    # 
 
194
    assert depth % 8 == 0
 
195
    packer = deblock
 
196
  else :
 
197
    assert 8 % depth == 0
 
198
    packer = bitfunge
 
199
 
 
200
  for x in packer(f, depth, width) :
 
201
    # x is the pixel as an unsigned integer
 
202
    o = []
 
203
    # This is a bit yucky.  Extract each channel from the _most_
 
204
    # significant part of x.
 
205
    for j in range(len(chan)) :
 
206
      v = (x >> (depth - chan[j])) & mask(chan[j])
 
207
      x <<= chan[j]
 
208
      if type[j] != 'x' :
 
209
        # scale to maxval
 
210
        v = v * float(maxval) / mask(chan[j])
 
211
        v = int(v+0.5)
 
212
        o.append(v)
 
213
    yield o
 
214
 
 
215
 
 
216
def decompress(f) :
 
217
  """Decompress a Plan 9 image file.  Assumes f is already cued past the
 
218
  initial 'compressed\n' string.
 
219
  """
 
220
 
 
221
  r = meta(f.read(60))
 
222
  return r, decomprest(f, r[4])
 
223
 
 
224
 
 
225
def decomprest(f, rows) :
 
226
  """Iterator that decompresses the rest of a file once the metadata
 
227
  have been consumed."""
 
228
 
 
229
  row = 0
 
230
  while row < rows :
 
231
    row,o = deblock(f)
 
232
    yield o
 
233
 
 
234
 
 
235
def deblock(f) :
 
236
  """Decompress a single block from a compressed Plan 9 image file.
 
237
  Each block starts with 2 decimal strings of 12 bytes each.  Yields a
 
238
  sequence of (row, data) pairs where row is the total number of rows
 
239
  processed according to the file format and data is the decompressed
 
240
  data for a set of rows."""
 
241
 
 
242
  row = int(f.read(12))
 
243
  size = int(f.read(12))
 
244
  if not (0 <= size <= 6000) :
 
245
    raise 'block has invalid size; not a Plan 9 image file?'
 
246
 
 
247
  # Since each block is at most 6000 bytes we may as well read it all in
 
248
  # one go.
 
249
  d = f.read(size)
 
250
  i = 0
 
251
  o = []
 
252
 
 
253
  while i < size :
 
254
    x = ord(d[i])
 
255
    i += 1
 
256
    if x & 0x80 :
 
257
      x = (x & 0x7f) + 1
 
258
      lit = d[i:i+x]
 
259
      i += x
 
260
      o.extend(lit)
 
261
      continue
 
262
    # x's high-order bit is 0
 
263
    l = (x >> 2) + 3
 
264
    # Offset is made from bottom 2 bits of x and all 8 bits of next
 
265
    # byte.  http://plan9.bell-labs.com/magic/man2html/6/image doesn't
 
266
    # say whether x's 2 bits are most signiificant or least significant.
 
267
    # But it is clear from inspecting a random file,
 
268
    # http://plan9.bell-labs.com/sources/plan9/sys/games/lib/sokoban/images/cargo.bit
 
269
    # that x's 2 bit are most significant.
 
270
    # 
 
271
    offset = (x & 3) << 8
 
272
    offset |= ord(d[i])
 
273
    i += 1
 
274
    # Note: complement operator neatly maps (0 to 1023) to (-1 to
 
275
    # -1024).  Adding len(o) gives a (non-negative) offset into o from
 
276
    # which to start indexing.
 
277
    offset = ~offset + len(o)
 
278
    if offset < 0 :
 
279
      raise 'byte offset indexes off the begininning of the output buffer; not a Plan 9 image file?'
 
280
    for j in range(l) :
 
281
      o.append(o[offset+j])
 
282
  return row,''.join(o)
 
283
 
 
284
def main(argv=None) :
 
285
  if argv is None :
 
286
    argv = sys.argv
 
287
  if len(sys.argv) <= 1 :
 
288
    return convert(sys.stdin)
 
289
  else :
 
290
    return convert(open(argv[1], 'rb'))
 
291
 
 
292
if __name__ == '__main__' :
 
293
  sys.exit(main())