-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpngdistill.py
58 lines (44 loc) · 1.56 KB
/
pngdistill.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/usr/bin/env python
from cStringIO import StringIO
from zopfli.zlib import compress
from zlib import decompress
import png
CHUNKS_TO_DROP = ['tEXt', 'iTXt', 'zTXt', 'prVW', 'mkBF', 'mkTS', 'mkBS', 'mkBT']
def _reduce_chunks(acc, c):
# Keep one, and only one, IDAT chunk as a placeholder for the re-compressed data
if c[0] == 'IDAT':
if not any(ec[0] == 'IDAT' for ec in acc):
acc.append(c)
elif c[0] not in CHUNKS_TO_DROP:
acc.append(c)
return acc
def distill(f=None, filename=None):
if filename:
f = open(filename)
if not f:
raise ValueError('No file was supplied to distill')
reader = png.Reader(f)
chunks = [(ct, c) for (ct, c) in reader.chunks()]
rebuilt_idat_chunk = build_idat_chunk(chunks)
distilled_chunks = reduce(_reduce_chunks, chunks, [])
def _remap_chunks(acc, c):
if c[0] == 'IDAT':
acc.append(rebuilt_idat_chunk)
else:
acc.append(c)
return acc
result_chunks = reduce(_remap_chunks, distilled_chunks, [])
result = StringIO()
png.write_chunks(result, result_chunks)
return result
def build_idat_chunk(chunks):
deflated = ''.join([chunk for (chunk_type, chunk) in chunks if chunk_type == 'IDAT'])
decompressed = decompress(deflated)
return ('IDAT', compress(decompressed))
if __name__ == "__main__":
from shutil import copyfileobj
import sys
result = distill(filename=sys.argv[1])
result.seek(0)
with open(sys.argv[2], 'w+') as output_file:
copyfileobj(result, output_file)