Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(182)

Side by Side Diff: tools/resources/ico_tools.py

Issue 1272763003: optimize-ico-files: Automatically convert large BMPs to PNG format. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@optimize-ico-files
Patch Set: Rebase. Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/app/theme/README ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2015 The Chromium Authors. All rights reserved. 1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # 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
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import logging 5 import logging
6 import math 6 import math
7 import os 7 import os
8 import struct 8 import struct
9 import subprocess 9 import subprocess
10 import sys 10 import sys
11 import tempfile 11 import tempfile
12 12
13 OPTIMIZE_PNG_FILES = 'tools/resources/optimize-png-files.sh' 13 OPTIMIZE_PNG_FILES = 'tools/resources/optimize-png-files.sh'
14 IMAGEMAGICK_CONVERT = 'convert'
14 15
15 logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') 16 logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
16 17
17 class InvalidFile(Exception): 18 class InvalidFile(Exception):
18 """Represents an invalid ICO file.""" 19 """Represents an invalid ICO file."""
19 20
20 def IsPng(png_data): 21 def IsPng(png_data):
21 """Determines whether a sequence of bytes is a PNG.""" 22 """Determines whether a sequence of bytes is a PNG."""
22 return png_data.startswith('\x89PNG\r\n\x1a\n') 23 return png_data.startswith('\x89PNG\r\n\x1a\n')
23 24
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
62 with open(png_filename, 'wb') as png_file: 63 with open(png_filename, 'wb') as png_file:
63 png_file.write(png_data) 64 png_file.write(png_data)
64 return OptimizePngFile(temp_dir, png_filename, 65 return OptimizePngFile(temp_dir, png_filename,
65 optimization_level=optimization_level) 66 optimization_level=optimization_level)
66 67
67 finally: 68 finally:
68 if os.path.exists(png_filename): 69 if os.path.exists(png_filename):
69 os.unlink(png_filename) 70 os.unlink(png_filename)
70 os.rmdir(temp_dir) 71 os.rmdir(temp_dir)
71 72
73 def ExportSingleEntry(icon_dir_entry, icon_data, outfile):
74 """Export a single icon dir entry to its own ICO file.
75
76 Args:
77 icon_dir_entry: Struct containing the fields of an ICONDIRENTRY.
78 icon_data: Raw pixel data of the icon.
79 outfile: File object to write to.
80 """
81 # Write the ICONDIR header.
82 logging.debug('len(icon_data) = %d', len(icon_data))
83 outfile.write(struct.pack('<HHH', 0, 1, 1))
84
85 # Write the ICONDIRENTRY header.
86 width, height, num_colors, r1, r2, r3, size, _ = icon_dir_entry
87 offset = 22;
88 icon_dir_entry = width, height, num_colors, r1, r2, r3, size, offset
89 outfile.write(struct.pack('<BBBBHHLL', *icon_dir_entry))
90
91 # Write the image data.
92 outfile.write(icon_data)
93
94 def ConvertIcoToPng(ico_filename, png_filename):
95 """Convert a single-entry ICO file to a PNG image.
96
97 Requires that the user has `convert` (ImageMagick) installed.
98
99 Raises:
100 OSError: If ImageMagick was not found.
101 subprocess.CalledProcessError: If convert failed.
102 """
103 logging.debug('Converting BMP image to PNG...')
104 args = [IMAGEMAGICK_CONVERT, ico_filename, png_filename]
105 result = subprocess.check_call(args, stdout=sys.stderr)
106 logging.info('Converted BMP image to PNG format')
107
108 def OptimizeBmp(icon_dir_entry, icon_data):
109 """Convert a BMP file to PNG and optimize it.
110
111 Args:
112 icon_dir_entry: Struct containing the fields of an ICONDIRENTRY.
113 icon_data: Raw pixel data of the icon.
114
115 Returns:
116 The raw bytes of a PNG file, an optimized version of the input.
117 """
118 temp_dir = tempfile.mkdtemp()
119 try:
120 logging.debug('temp_dir = %s', temp_dir)
121 ico_filename = os.path.join(temp_dir, 'image.ico')
122 png_filename = os.path.join(temp_dir, 'image.png')
123 with open(ico_filename, 'wb') as ico_file:
124 logging.debug('writing %s', ico_filename)
125 ExportSingleEntry(icon_dir_entry, icon_data, ico_file)
126
127 try:
128 ConvertIcoToPng(ico_filename, png_filename)
129 except Exception as e:
130 logging.warning('Could not convert BMP to PNG format: %s', e)
131 if isinstance(e, OSError):
132 logging.info('This is because ImageMagick (`convert`) was not found. '
133 'Please install it, or manually convert large BMP images '
134 'into PNG before running this utility.')
135 return icon_data
136
137 return OptimizePngFile(temp_dir, png_filename)
138
139 finally:
140 if os.path.exists(ico_filename):
141 os.unlink(ico_filename)
142 if os.path.exists(png_filename):
143 os.unlink(png_filename)
144 os.rmdir(temp_dir)
145
72 def ComputeANDMaskFromAlpha(image_data, width, height): 146 def ComputeANDMaskFromAlpha(image_data, width, height):
73 """Compute an AND mask from 32-bit BGRA image data.""" 147 """Compute an AND mask from 32-bit BGRA image data."""
74 and_bytes = [] 148 and_bytes = []
75 for y in range(height): 149 for y in range(height):
76 bit_count = 0 150 bit_count = 0
77 current_byte = 0 151 current_byte = 0
78 for x in range(width): 152 for x in range(width):
79 alpha = image_data[(y * width + x) * 4 + 3] 153 alpha = image_data[(y * width + x) * 4 + 3]
80 current_byte <<= 1 154 current_byte <<= 1
81 if ord(alpha) == 0: 155 if ord(alpha) == 0:
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
168 offset = current_offset 242 offset = current_offset
169 icon_data = infile.read(size) 243 icon_data = infile.read(size)
170 if len(icon_data) != size: 244 if len(icon_data) != size:
171 raise EOFError() 245 raise EOFError()
172 246
173 entry_is_png = IsPng(icon_data) 247 entry_is_png = IsPng(icon_data)
174 logging.info('%s entry #%d: %dx%d, %d bytes (%s)', filename, i + 1, width, 248 logging.info('%s entry #%d: %dx%d, %d bytes (%s)', filename, i + 1, width,
175 height, size, 'PNG' if entry_is_png else 'BMP') 249 height, size, 'PNG' if entry_is_png else 'BMP')
176 250
177 if entry_is_png: 251 if entry_is_png:
252 # It is a PNG. Crush it.
178 icon_data = OptimizePng(icon_data, optimization_level=optimization_level) 253 icon_data = OptimizePng(icon_data, optimization_level=optimization_level)
254 elif width >= 256 or height >= 256:
255 # It is a large BMP. Reformat as a PNG, then crush it.
256 # Note: Smaller images must be kept uncompressed, for compatibility
257 # with Windows XP.
scottmg 2016/11/07 17:45:35 If this is really the only reason for this restric
Matt Giuca 2016/11/08 00:06:31 That's a great suggestion. I think so, but I'd hav
258 icon_data = OptimizeBmp(icon_dir_entries[i], icon_data)
179 else: 259 else:
180 new_icon_data = RebuildANDMask(icon_data) 260 new_icon_data = RebuildANDMask(icon_data)
181 if new_icon_data != icon_data: 261 if new_icon_data != icon_data:
182 logging.info(' * Rebuilt AND mask for this image from alpha channel.') 262 logging.info(' * Rebuilt AND mask for this image from alpha channel.')
183 icon_data = new_icon_data 263 icon_data = new_icon_data
184 264
185 if width >= 256 or height >= 256:
186 # TODO(mgiuca): Automatically convert large BMP images to PNGs.
187 logging.warning('Entry #%d is a large image in uncompressed BMP '
188 'format. Please manually convert to PNG format before '
189 'running this utility.', i + 1)
190
191 new_size = len(icon_data) 265 new_size = len(icon_data)
192 current_offset += new_size 266 current_offset += new_size
193 icon_dir_entries[i] = (width % 256, height % 256, num_colors, r1, r2, r3, 267 icon_dir_entries[i] = (width % 256, height % 256, num_colors, r1, r2, r3,
194 new_size, offset) 268 new_size, offset)
195 icon_bitmap_data.append(icon_data) 269 icon_bitmap_data.append(icon_data)
196 270
197 # Write the data back to outfile. 271 # Write the data back to outfile.
198 outfile.write(icondir) 272 outfile.write(icondir)
199 for icon_dir_entry in icon_dir_entries: 273 for icon_dir_entry in icon_dir_entries:
200 outfile.write(struct.pack('<BBBBHHLL', *icon_dir_entry)) 274 outfile.write(struct.pack('<BBBBHHLL', *icon_dir_entry))
201 for icon_bitmap in icon_bitmap_data: 275 for icon_bitmap in icon_bitmap_data:
202 outfile.write(icon_bitmap) 276 outfile.write(icon_bitmap)
OLDNEW
« no previous file with comments | « chrome/app/theme/README ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698