| OLD | NEW |
| 1 #!/usr/bin/env python | |
| 2 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 5 | 4 |
| 6 """Windows ICO file crusher. | |
| 7 | |
| 8 Optimizes the PNG images within a Windows ICO icon file. This extracts all of | |
| 9 the sub-images within the file, runs any PNG-formatted images through | |
| 10 optimize-png-files.sh, then packs them back into an ICO file. | |
| 11 | |
| 12 NOTE: ICO files can contain both raw uncompressed BMP files and PNG files. This | |
| 13 script does not touch the BMP files, which means if you have a huge uncompressed | |
| 14 image, it will not get smaller. 256x256 icons should be PNG-formatted first. | |
| 15 (Smaller icons should be BMPs for compatibility with Windows XP.) | |
| 16 """ | |
| 17 | |
| 18 import argparse | |
| 19 import logging | 5 import logging |
| 20 import math | 6 import math |
| 21 import os | 7 import os |
| 22 import StringIO | |
| 23 import struct | 8 import struct |
| 24 import subprocess | 9 import subprocess |
| 25 import sys | 10 import sys |
| 26 import tempfile | 11 import tempfile |
| 27 | 12 |
| 28 OPTIMIZE_PNG_FILES = 'tools/resources/optimize-png-files.sh' | 13 OPTIMIZE_PNG_FILES = 'tools/resources/optimize-png-files.sh' |
| 29 | 14 |
| 30 logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') | 15 logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') |
| 31 | 16 |
| 32 class InvalidFile(Exception): | 17 class InvalidFile(Exception): |
| (...skipping 175 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 208 icon_dir_entries[i] = (width % 256, height % 256, num_colors, r1, r2, r3, | 193 icon_dir_entries[i] = (width % 256, height % 256, num_colors, r1, r2, r3, |
| 209 new_size, offset) | 194 new_size, offset) |
| 210 icon_bitmap_data.append(icon_data) | 195 icon_bitmap_data.append(icon_data) |
| 211 | 196 |
| 212 # Write the data back to outfile. | 197 # Write the data back to outfile. |
| 213 outfile.write(icondir) | 198 outfile.write(icondir) |
| 214 for icon_dir_entry in icon_dir_entries: | 199 for icon_dir_entry in icon_dir_entries: |
| 215 outfile.write(struct.pack('<BBBBHHLL', *icon_dir_entry)) | 200 outfile.write(struct.pack('<BBBBHHLL', *icon_dir_entry)) |
| 216 for icon_bitmap in icon_bitmap_data: | 201 for icon_bitmap in icon_bitmap_data: |
| 217 outfile.write(icon_bitmap) | 202 outfile.write(icon_bitmap) |
| 218 | |
| 219 def main(args=None): | |
| 220 if args is None: | |
| 221 args = sys.argv[1:] | |
| 222 | |
| 223 parser = argparse.ArgumentParser(description='Crush Windows ICO files.') | |
| 224 parser.add_argument('files', metavar='ICO', type=argparse.FileType('r+b'), | |
| 225 nargs='+', help='.ico files to be crushed') | |
| 226 parser.add_argument('-o', dest='optimization_level', metavar='OPT', type=int, | |
| 227 help='optimization level') | |
| 228 parser.add_argument('-d', '--debug', dest='debug', action='store_true', | |
| 229 help='enable debug logging') | |
| 230 | |
| 231 args = parser.parse_args() | |
| 232 | |
| 233 if args.debug: | |
| 234 logging.getLogger().setLevel(logging.DEBUG) | |
| 235 | |
| 236 for file in args.files: | |
| 237 buf = StringIO.StringIO() | |
| 238 file.seek(0, os.SEEK_END) | |
| 239 old_length = file.tell() | |
| 240 file.seek(0, os.SEEK_SET) | |
| 241 OptimizeIcoFile(file, buf, args.optimization_level) | |
| 242 | |
| 243 new_length = len(buf.getvalue()) | |
| 244 | |
| 245 # Always write (even if file size not reduced), because we make other fixes | |
| 246 # such as regenerating the AND mask. | |
| 247 file.truncate(new_length) | |
| 248 file.seek(0) | |
| 249 file.write(buf.getvalue()) | |
| 250 | |
| 251 if new_length >= old_length: | |
| 252 logging.info('%s : Could not reduce file size.', file.name) | |
| 253 else: | |
| 254 saving = old_length - new_length | |
| 255 saving_percent = float(saving) / old_length | |
| 256 logging.info('%s : %d => %d (%d bytes : %d %%)', file.name, old_length, | |
| 257 new_length, saving, int(saving_percent * 100)) | |
| 258 | |
| 259 if __name__ == '__main__': | |
| 260 sys.exit(main()) | |
| OLD | NEW |